@shendeguize/dsh-agent-sidecar 0.1.0 → 0.2.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 (58) hide show
  1. package/README.md +78 -11
  2. package/lib/client.js +4708 -2932
  3. package/lib/client.js.map +1 -1
  4. package/package.json +3 -1
  5. package/src/client/analysis/AnalysisPanel.tsx +19 -27
  6. package/src/client/analysis/analysis.module.css +36 -97
  7. package/src/client/board/Board.tsx +255 -48
  8. package/src/client/board/board.module.css +113 -92
  9. package/src/client/board/logic.ts +99 -21
  10. package/src/client/board/project-view-logic.ts +27 -34
  11. package/src/client/board/project-view.module.css +46 -83
  12. package/src/client/board/project-view.tsx +50 -8
  13. package/src/client/board/strings.ts +70 -85
  14. package/src/client/commands.ts +30 -15
  15. package/src/client/controller.ts +27 -7
  16. package/src/client/detail/SessionDetail.tsx +234 -37
  17. package/src/client/detail/detail.module.css +100 -153
  18. package/src/client/detail/logic.ts +220 -29
  19. package/src/client/detail/strings.ts +66 -77
  20. package/src/client/detail-glue.ts +33 -0
  21. package/src/client/detail-view.module.css +43 -35
  22. package/src/client/detail-view.tsx +138 -118
  23. package/src/client/dsh-tools/LineageTree.tsx +22 -13
  24. package/src/client/dsh-tools/SearchPanel.tsx +18 -11
  25. package/src/client/dsh-tools/dsh-tools.module.css +53 -140
  26. package/src/client/dsh-tools/strings.ts +42 -77
  27. package/src/client/index.ts +285 -124
  28. package/src/client/inject/InjectPanel.tsx +81 -61
  29. package/src/client/inject/inject.module.css +97 -197
  30. package/src/client/inject/logic.ts +38 -0
  31. package/src/client/lifecycle/handoff.ts +83 -0
  32. package/src/client/locales/en.ts +91 -4
  33. package/src/client/locales/host.ts +131 -0
  34. package/src/client/locales/index.ts +196 -8
  35. package/src/client/locales/react.ts +10 -0
  36. package/src/client/locales/view.ts +38 -0
  37. package/src/client/locales/zh.ts +200 -125
  38. package/src/client/mount.tsx +75 -41
  39. package/src/client/navigation/CenterOverlay.tsx +40 -0
  40. package/src/client/navigation/center-overlay.module.css +51 -0
  41. package/src/client/navigation/center.ts +45 -0
  42. package/src/client/navigation/modal-isolation.ts +152 -0
  43. package/src/client/navigation/modal-surface-anchor.ts +72 -0
  44. package/src/client/navigation/sidebar-entry.module.css +73 -0
  45. package/src/client/navigation/sidebar-entry.ts +309 -0
  46. package/src/client/primitives/StaticPill.tsx +21 -0
  47. package/src/client/settings-card.module.css +35 -119
  48. package/src/client/settings-card.tsx +31 -153
  49. package/src/client/settings-fields.tsx +131 -0
  50. package/src/client/sidebar/SidebarTab.tsx +156 -0
  51. package/src/client/sidebar/model.ts +65 -0
  52. package/src/client/sidebar/sidebar-tab.module.css +118 -0
  53. package/src/client/sidebar-tab.tsx +46 -320
  54. package/src/client/theme/agsc.module.css +31 -0
  55. package/src/client/theme/parts.ts +56 -0
  56. package/src/client/ui-integration.ts +86 -0
  57. package/src/client/widget.tsx +42 -16
  58. package/src/client/inject/overlay.module.css +0 -22
@@ -0,0 +1,309 @@
1
+ /** First-class Agent Center row inserted after the shell's New Session row. */
2
+ import type { CenterNavigation } from './center.ts'
3
+ import css from './sidebar-entry.module.css'
4
+ import { PLUGIN_DOM_ID, surfaceProps } from '../theme/parts.ts'
5
+
6
+ export const SIDEBAR_ENTRY_SELECTOR = '[data-agent-sidecar-sidebar-entry]'
7
+ const ENTRY_ATTRIBUTE = 'data-agent-sidecar-sidebar-entry'
8
+ const LABEL_ATTRIBUTE = 'data-agent-sidecar-sidebar-entry-label'
9
+ const SVG_NS = 'http://www.w3.org/2000/svg'
10
+ const ENTRY_BINDING = Symbol.for('@shendeguize/dsh-agent-sidecar/sidebar-entry-binding')
11
+ const ENTRY_BRAND = Symbol.for('@shendeguize/dsh-agent-sidecar/sidebar-entry-brand')
12
+ const ENTRY_DISPATCHER = Symbol.for('@shendeguize/dsh-agent-sidecar/sidebar-entry-dispatcher')
13
+ const warnedForeignEntries = new WeakSet<object>()
14
+
15
+ interface QueryScope { querySelector(selector: string): unknown | null }
16
+ export interface SidebarEntryCopyPort {
17
+ readonly label: string
18
+ readonly accessibilityLabel: string
19
+ subscribe(listener: () => void): () => void
20
+ }
21
+ export interface SidebarEntryCopyTarget {
22
+ readonly button: {
23
+ title: string
24
+ setAttribute(name: string, value: string): void
25
+ }
26
+ readonly label: { textContent: string | null }
27
+ }
28
+ interface SidebarEntryElements extends SidebarEntryCopyTarget { readonly button: HTMLButtonElement }
29
+ interface SidebarEntryObserver { disconnect(): void }
30
+ interface SidebarEntryBinding {
31
+ readonly owner: object
32
+ readonly openCenter: CenterNavigation
33
+ stopCopy: () => void
34
+ observer: SidebarEntryObserver | null
35
+ }
36
+ export interface SidebarEntryOwnerTarget extends SidebarEntryCopyTarget {
37
+ readonly button: SidebarEntryCopyTarget['button'] & { remove(): void }
38
+ }
39
+ /** Narrow, DOM-independent idempotency check used by mount and unit tests. */
40
+ export function hasSidebarEntry(scope: QueryScope): boolean {
41
+ return scope.querySelector(SIDEBAR_ENTRY_SELECTOR) !== null
42
+ }
43
+ export function applyCopy(target: SidebarEntryCopyTarget, copy: SidebarEntryCopyPort): void {
44
+ target.label.textContent = copy.label
45
+ target.button.setAttribute('aria-label', copy.accessibilityLabel)
46
+ target.button.title = copy.accessibilityLabel
47
+ }
48
+
49
+ export function bindSidebarEntryCopy(
50
+ target: SidebarEntryCopyTarget,
51
+ copy: SidebarEntryCopyPort,
52
+ ): () => void {
53
+ const update = (): void => { applyCopy(target, copy) }
54
+ update()
55
+ return copy.subscribe(update)
56
+ }
57
+
58
+ function bindingOf(button: object): SidebarEntryBinding | undefined {
59
+ return (button as Record<PropertyKey, unknown>)[ENTRY_BINDING] as SidebarEntryBinding | undefined
60
+ }
61
+ function hasCurrentDispatcher(button: object): boolean {
62
+ const record = button as Record<PropertyKey, unknown>
63
+ return (record[ENTRY_BRAND] === true || record[ENTRY_BINDING] !== undefined)
64
+ && record[ENTRY_DISPATCHER] === true
65
+ }
66
+ /** A pure, DOM-independent ownership check; the idempotency attribute alone is foreign. */
67
+ export function isOwnedSidebarEntry(candidate: unknown): boolean {
68
+ if (candidate === null
69
+ || (typeof candidate !== 'object' && typeof candidate !== 'function')) return false
70
+ const record = candidate as Record<PropertyKey, unknown>
71
+ if (record[ENTRY_BRAND] === true || record[ENTRY_BINDING] !== undefined) return true
72
+ const element = candidate as {
73
+ tagName?: unknown
74
+ getAttribute?: (name: string) => string | null
75
+ querySelector?: (selector: string) => unknown | null
76
+ }
77
+ return element.tagName === 'BUTTON'
78
+ && element.getAttribute?.('data-dsh-plugin') === PLUGIN_DOM_ID
79
+ && element.getAttribute?.('data-dsh-part') === 'sidebar-entry'
80
+ && element.querySelector?.(`[${LABEL_ATTRIBUTE}]`) != null
81
+ }
82
+
83
+ function brandSidebarEntry(button: object): void {
84
+ (button as Record<PropertyKey, unknown>)[ENTRY_BRAND] = true
85
+ }
86
+ function installClickDispatcher(button: HTMLButtonElement): void {
87
+ const record = button as unknown as Record<PropertyKey, unknown>
88
+ if (record[ENTRY_DISPATCHER] === true) return
89
+ button.addEventListener('click', () => { openBoundSidebarEntry(button) })
90
+ record[ENTRY_DISPATCHER] = true
91
+ }
92
+ function once(dispose: () => void): () => void {
93
+ let active = true
94
+ return () => {
95
+ if (!active) return
96
+ active = false
97
+ dispose()
98
+ }
99
+ }
100
+ /** Invoke the latest cross-bundle binding, never a captured HMR closure. */
101
+ export function openBoundSidebarEntry(button: object): void {
102
+ try {
103
+ bindingOf(button)?.openCenter()
104
+ } catch {
105
+ // Navigation is a best-effort bridge to host-owned tab DOM.
106
+ }
107
+ }
108
+ /** Latest-owner-wins binding over one shared DOM button. */
109
+ export function bindSidebarEntryOwner(
110
+ target: SidebarEntryOwnerTarget,
111
+ owner: object,
112
+ openCenter: CenterNavigation,
113
+ copy: SidebarEntryCopyPort,
114
+ startObserver: () => SidebarEntryObserver | null,
115
+ ): () => void {
116
+ const previous = bindingOf(target.button)
117
+ previous?.observer?.disconnect()
118
+ previous?.stopCopy()
119
+
120
+ const binding: SidebarEntryBinding = {
121
+ owner,
122
+ openCenter,
123
+ stopCopy: () => {},
124
+ observer: null,
125
+ }
126
+ const sharedButton = target.button as unknown as Record<PropertyKey, unknown>
127
+ sharedButton[ENTRY_BINDING] = binding
128
+ binding.stopCopy = once(bindSidebarEntryCopy(target, copy))
129
+ binding.observer = startObserver()
130
+
131
+ return () => {
132
+ binding.observer?.disconnect()
133
+ if (bindingOf(target.button)?.owner !== owner) return
134
+ binding.stopCopy()
135
+ delete sharedButton[ENTRY_BINDING]
136
+ target.button.remove()
137
+ }
138
+ }
139
+
140
+ function createIcon(): SVGSVGElement {
141
+ const icon = document.createElementNS(SVG_NS, 'svg')
142
+ for (const [name, value] of Object.entries({
143
+ viewBox: '0 0 16 16',
144
+ width: '16',
145
+ height: '16',
146
+ fill: 'none',
147
+ stroke: 'currentColor',
148
+ 'stroke-width': '1.4',
149
+ 'stroke-linecap': 'round',
150
+ 'stroke-linejoin': 'round',
151
+ 'aria-hidden': 'true',
152
+ focusable: 'false',
153
+ })) {
154
+ icon.setAttribute(name, value)
155
+ }
156
+ const orbit = document.createElementNS(SVG_NS, 'circle')
157
+ orbit.setAttribute('cx', '8')
158
+ orbit.setAttribute('cy', '8')
159
+ orbit.setAttribute('r', '5.5')
160
+ const center = document.createElementNS(SVG_NS, 'circle')
161
+ center.setAttribute('cx', '8')
162
+ center.setAttribute('cy', '8')
163
+ center.setAttribute('r', '1.75')
164
+ const path = document.createElementNS(SVG_NS, 'path')
165
+ path.setAttribute('d', 'M8 2.5v3.75M8 9.75v3.75M2.5 8h3.75M9.75 8h3.75')
166
+ icon.append(orbit, center, path)
167
+ return icon
168
+ }
169
+
170
+ function createEntry(): SidebarEntryElements {
171
+ const entry = document.createElement('button')
172
+ brandSidebarEntry(entry)
173
+ const surface = surfaceProps('sidebar-entry', css.entry)
174
+ entry.type = 'button'
175
+ entry.className = surface.className
176
+ entry.setAttribute(ENTRY_ATTRIBUTE, '')
177
+ entry.setAttribute('data-dsh-plugin', surface['data-dsh-plugin'])
178
+ entry.setAttribute('data-dsh-part', surface['data-dsh-part'])
179
+
180
+ const icon = document.createElement('span')
181
+ icon.className = css.entryIcon ?? ''
182
+ icon.setAttribute('aria-hidden', 'true')
183
+ icon.appendChild(createIcon())
184
+ const label = document.createElement('span')
185
+ label.className = css.entryLabel ?? ''
186
+ label.setAttribute(LABEL_ATTRIBUTE, '')
187
+ entry.append(icon, label)
188
+ installClickDispatcher(entry)
189
+ return { button: entry, label }
190
+ }
191
+
192
+ function existingEntry(button: HTMLButtonElement): SidebarEntryElements | null {
193
+ const label = button.querySelector<HTMLElement>(`[${LABEL_ATTRIBUTE}]`)
194
+ ?? (button.lastElementChild as HTMLElement | null)
195
+ return label === null ? null : { button, label }
196
+ }
197
+
198
+ function rejectLegacyEntry(): null {
199
+ console.warn('[agent-sidecar] Sidebar legacy entry cannot be safely replaced; leaving it untouched.')
200
+ return null
201
+ }
202
+
203
+ function replaceLegacyEntry(button: HTMLButtonElement): SidebarEntryElements | null {
204
+ if (typeof button.cloneNode !== 'function' || typeof button.replaceWith !== 'function') {
205
+ return rejectLegacyEntry()
206
+ }
207
+ let clone: HTMLButtonElement
208
+ try {
209
+ clone = button.cloneNode(true) as HTMLButtonElement
210
+ } catch {
211
+ return rejectLegacyEntry()
212
+ }
213
+ const elements = clone.tagName === 'BUTTON' && typeof clone.addEventListener === 'function'
214
+ ? existingEntry(clone)
215
+ : null
216
+ if (elements === null) return rejectLegacyEntry()
217
+ try {
218
+ button.replaceWith(clone)
219
+ } catch {
220
+ return rejectLegacyEntry()
221
+ }
222
+ brandSidebarEntry(clone)
223
+ installClickDispatcher(clone)
224
+ return elements
225
+ }
226
+
227
+ function sidebarRoot(): HTMLElement | null {
228
+ const column = document.querySelector<HTMLElement>(
229
+ '[data-pane="sidebar"], [class*="sidebarCol"]',
230
+ )
231
+ if (column === null) return null
232
+ const logoOwner = column.querySelector<HTMLElement>('[class*="logoRow"]')?.parentElement
233
+ return logoOwner ?? (column.firstElementChild as HTMLElement | null)
234
+ }
235
+
236
+ function newSessionRow(root: HTMLElement): Element | null {
237
+ const nested = root.querySelector<HTMLButtonElement>('button[class*="newSession"]')
238
+ const button = nested ?? Array.from(root.children).find((child) => child.tagName === 'BUTTON')
239
+ if (button === undefined) return null
240
+ const row = button.closest('[class*="logoRow"]')
241
+ if (row !== null && row.parentElement === root) return row
242
+ return button.parentElement === root ? button : null
243
+ }
244
+
245
+ function placeEntry(entry: HTMLButtonElement): boolean {
246
+ const root = sidebarRoot()
247
+ if (root === null) return false
248
+ const anchor = newSessionRow(root)
249
+ if (anchor === null) return false
250
+ if (entry.parentElement !== root || anchor.nextElementSibling !== entry) {
251
+ root.insertBefore(entry, anchor.nextElementSibling)
252
+ }
253
+ return true
254
+ }
255
+
256
+ /**
257
+ * Wait for the sidebar, restore the row after React rebuilds, and return full
258
+ * cleanup. Overlapping applies synchronously adopt the existing row.
259
+ */
260
+ export function mountSidebarEntry(
261
+ openCenter: CenterNavigation,
262
+ copy: SidebarEntryCopyPort,
263
+ ): () => void {
264
+ if (typeof document === 'undefined') return () => {}
265
+ const candidate = document.querySelector<HTMLButtonElement>(SIDEBAR_ENTRY_SELECTOR)
266
+ if (candidate !== null && !isOwnedSidebarEntry(candidate)) {
267
+ if (!warnedForeignEntries.has(candidate)) {
268
+ warnedForeignEntries.add(candidate)
269
+ console.warn('[agent-sidecar] Sidebar entry collision: refusing to modify a foreign '
270
+ + SIDEBAR_ENTRY_SELECTOR + ' node.')
271
+ }
272
+ return () => {}
273
+ }
274
+ const elements = candidate === null
275
+ ? createEntry()
276
+ : hasCurrentDispatcher(candidate)
277
+ ? existingEntry(candidate)
278
+ : replaceLegacyEntry(candidate)
279
+ if (elements === null) return () => {}
280
+ brandSidebarEntry(elements.button)
281
+ const entry = elements.button
282
+ const owner = {}
283
+ let disposed = false
284
+ const ensurePlaced = (): void => {
285
+ if (disposed) return
286
+ if (entry.isConnected) return
287
+ const existing = document.querySelector(SIDEBAR_ENTRY_SELECTOR)
288
+ if (existing !== null && existing !== entry) return
289
+ placeEntry(entry)
290
+ }
291
+ ensurePlaced()
292
+ const disposeBinding = bindSidebarEntryOwner(
293
+ elements,
294
+ owner,
295
+ openCenter,
296
+ copy,
297
+ () => {
298
+ if (typeof MutationObserver === 'undefined') return null
299
+ const observer = new MutationObserver(ensurePlaced)
300
+ observer.observe(document.body, { childList: true, subtree: true })
301
+ return observer
302
+ },
303
+ )
304
+ return () => {
305
+ if (disposed) return
306
+ disposed = true
307
+ disposeBinding()
308
+ }
309
+ }
@@ -0,0 +1,21 @@
1
+ import { Pill } from '@deepseek-ai/dsh-client-ui-primitives'
2
+ import type { HTMLAttributes, ReactElement } from 'react'
3
+
4
+ export type StaticPillProps = HTMLAttributes<HTMLSpanElement>
5
+
6
+ /**
7
+ * Preserve native span attributes around the rc.2 Pill static branch, which
8
+ * currently omits its rest props. The inner primitive remains the sole owner
9
+ * of the DSH pill visuals.
10
+ */
11
+ export function StaticPill({
12
+ className,
13
+ children,
14
+ ...rest
15
+ }: StaticPillProps): ReactElement {
16
+ return (
17
+ <span className={className} {...rest}>
18
+ <Pill>{children}</Pill>
19
+ </span>
20
+ )
21
+ }
@@ -1,12 +1,13 @@
1
- /* Agent Sidecar settings card. Design language follows the shipped
2
- * ui-settings-plugins PluginCard/fields chrome (disclosure header over a
3
- * sectioned form body); every color rides a dsh --dsw-alias-* design token,
4
- * no literal colors. */
1
+ /* Layout specific to the Agent Sidecar settings composition. Control chrome
2
+ * comes from dsh primitives; native select/checkbox styles stay token-backed. */
5
3
 
6
4
  .card {
5
+ width: 100%;
6
+ max-width: 760px;
7
+ box-sizing: border-box;
7
8
  list-style: none;
8
9
  border: 1px solid var(--dsw-alias-border-l2);
9
- border-radius: 12px;
10
+ border-radius: var(--agsc-radius-card);
10
11
  background: var(--dsw-alias-bg-layer-3);
11
12
  transition: border-color .16s, background .16s;
12
13
  }
@@ -20,8 +21,6 @@
20
21
  border-color: var(--dsw-alias-label-dimmed);
21
22
  }
22
23
 
23
- /* ── disclosure header ─────────────────────────────────────────────────── */
24
-
25
24
  .header {
26
25
  width: 100%;
27
26
  appearance: none;
@@ -35,7 +34,7 @@
35
34
  align-items: center;
36
35
  gap: 12px;
37
36
  padding: 14px 16px;
38
- border-radius: 12px;
37
+ border-radius: var(--agsc-radius-card);
39
38
  }
40
39
 
41
40
  .header:focus-visible {
@@ -64,37 +63,21 @@
64
63
  color: var(--dsw-alias-label-tertiary);
65
64
  }
66
65
 
67
- /* Carried on the header so a collapsed card still says it holds edits. */
68
66
  .pending {
69
67
  flex: none;
70
- border-radius: 999px;
71
- padding: 1px 8px;
72
- font-size: 11px;
73
- line-height: 17px;
74
- font-weight: 500;
75
68
  white-space: nowrap;
76
- background: var(--dsw-alias-bg-module-platform);
77
- color: var(--dsw-alias-label-secondary);
78
69
  }
79
70
 
80
- /* CSS-drawn caret (ui-primitives icons are not on this package's dependency
81
- * surface, so the chevron is a rotated border square). */
82
71
  .chevron {
83
72
  flex: none;
84
- width: 8px;
85
- height: 8px;
86
- border-right: 1.5px solid var(--dsw-alias-label-tertiary);
87
- border-bottom: 1.5px solid var(--dsw-alias-label-tertiary);
88
- transform: rotate(45deg) translateY(-2px);
73
+ color: var(--dsw-alias-label-tertiary);
89
74
  transition: transform .16s;
90
75
  }
91
76
 
92
77
  .chevronOpen {
93
- transform: rotate(225deg) translateY(-2px);
78
+ transform: rotate(180deg);
94
79
  }
95
80
 
96
- /* ── body ──────────────────────────────────────────────────────────────── */
97
-
98
81
  .body {
99
82
  border-top: 1px solid var(--dsw-alias-border-l2);
100
83
  margin: 0 16px;
@@ -124,8 +107,6 @@
124
107
  color: var(--dsw-alias-label-primary);
125
108
  }
126
109
 
127
- /* ── fields ────────────────────────────────────────────────────────────── */
128
-
129
110
  .field {
130
111
  padding: 8px 0;
131
112
  display: flex;
@@ -150,46 +131,47 @@
150
131
  margin: 0;
151
132
  font-size: 12px;
152
133
  line-height: 1.5;
153
- color: var(--dsw-alias-label-error);
134
+ color: var(--dsw-alias-state-error-primary);
135
+ }
136
+
137
+ .input {
138
+ width: 100%;
139
+ max-width: 420px;
140
+ box-sizing: border-box;
141
+ }
142
+
143
+ .inputInvalid {
144
+ border-color: var(--dsw-alias-state-error-primary);
145
+ }
146
+
147
+ .input:has(input:disabled) {
148
+ opacity: .5;
154
149
  }
155
150
 
156
- .input,
157
151
  .select {
158
- appearance: none;
152
+ width: 100%;
159
153
  max-width: 420px;
154
+ height: 32px;
155
+ box-sizing: border-box;
160
156
  border: 1px solid var(--dsw-alias-border-l2);
161
- border-radius: 8px;
162
- background: var(--dsw-alias-bg-layer-3);
163
- padding: 6px 10px;
157
+ border-radius: var(--agsc-radius-control);
158
+ background: var(--dsw-alias-bg-layer-1);
159
+ padding: 0 8px;
164
160
  font: inherit;
165
161
  font-size: 13px;
166
162
  line-height: 1.5;
167
163
  color: var(--dsw-alias-label-primary);
168
164
  }
169
165
 
170
- .input:focus-visible,
171
166
  .select:focus-visible {
172
167
  outline: none;
173
168
  border-color: var(--dsw-alias-brand-primary);
174
169
  }
175
170
 
176
- .input::placeholder {
177
- color: var(--dsw-alias-label-tertiary);
178
- }
179
-
180
- .input:disabled,
181
171
  .select:disabled {
182
172
  opacity: 0.5;
183
173
  cursor: default;
184
174
  }
185
-
186
- /* Modifier applied ALONGSIDE .input (no `composes`: the tsdown class-map
187
- * emitter keeps only the local hashed name and would drop composed bases). */
188
- .inputInvalid {
189
- border-color: var(--dsw-alias-label-error);
190
- }
191
-
192
- /* Toggle row: label text left, native checkbox right-aligned with it. */
193
175
  .toggleRow {
194
176
  display: flex;
195
177
  align-items: center;
@@ -209,8 +191,6 @@
209
191
  cursor: default;
210
192
  }
211
193
 
212
- /* ── notes (safety / daemon guidance) ──────────────────────────────────── */
213
-
214
194
  .note {
215
195
  margin: 6px 0 2px;
216
196
  border-radius: 8px;
@@ -221,8 +201,6 @@
221
201
  color: var(--dsw-alias-label-secondary);
222
202
  }
223
203
 
224
- /* ── daemon status row ─────────────────────────────────────────────────── */
225
-
226
204
  .statusRow {
227
205
  display: flex;
228
206
  align-items: center;
@@ -235,16 +213,15 @@
235
213
  width: 8px;
236
214
  height: 8px;
237
215
  border-radius: 50%;
238
- background: var(--dsw-alias-label-tertiary);
216
+ background: var(--agsc-fg-dimmed);
239
217
  }
240
218
 
241
- /* Modifiers applied alongside .statusDot (same no-`composes` rationale). */
242
219
  .statusOk {
243
- background: var(--dsw-alias-brand-primary);
220
+ background: var(--agsc-ok);
244
221
  }
245
222
 
246
223
  .statusError {
247
- background: var(--dsw-alias-label-error);
224
+ background: var(--agsc-err);
248
225
  }
249
226
 
250
227
  .statusText {
@@ -260,30 +237,9 @@
260
237
  }
261
238
 
262
239
  .retry {
263
- appearance: none;
264
- border: 1px solid var(--dsw-alias-border-l2);
265
- border-radius: 8px;
266
- background: none;
267
- padding: 3px 12px;
268
- font: inherit;
269
- font-size: 12px;
270
- line-height: 1.5;
271
- color: var(--dsw-alias-label-secondary);
272
- cursor: pointer;
273
- }
274
-
275
- .retry:hover:not(:disabled) {
276
- color: var(--dsw-alias-label-primary);
277
- border-color: var(--dsw-alias-label-dimmed);
278
- }
279
-
280
- .retry:focus-visible {
281
- outline: 2px solid var(--dsw-alias-brand-primary);
282
- outline-offset: 1px;
240
+ margin-left: auto;
283
241
  }
284
242
 
285
- /* ── footer ────────────────────────────────────────────────────────────── */
286
-
287
243
  .footer {
288
244
  display: flex;
289
245
  align-items: center;
@@ -311,49 +267,9 @@
311
267
  text-align: right;
312
268
  font-size: 12px;
313
269
  line-height: 1.5;
314
- color: var(--dsw-alias-label-error);
270
+ color: var(--dsw-alias-state-error-primary);
315
271
  }
316
272
 
317
273
  .spacer {
318
274
  flex: 1;
319
275
  }
320
-
321
- .discard,
322
- .save {
323
- appearance: none;
324
- border: 1px solid transparent;
325
- border-radius: 8px;
326
- padding: 5px 14px;
327
- font: inherit;
328
- font-size: 13px;
329
- line-height: 1.5;
330
- cursor: pointer;
331
- }
332
-
333
- .discard {
334
- border-color: var(--dsw-alias-border-l2);
335
- background: none;
336
- color: var(--dsw-alias-label-secondary);
337
- }
338
-
339
- .discard:hover:not(:disabled) {
340
- color: var(--dsw-alias-label-primary);
341
- border-color: var(--dsw-alias-label-dimmed);
342
- }
343
-
344
- .save {
345
- background: var(--dsw-alias-label-primary);
346
- color: var(--dsw-alias-bg-layer-3);
347
- }
348
-
349
- .discard:disabled,
350
- .save:disabled {
351
- opacity: 0.4;
352
- cursor: default;
353
- }
354
-
355
- .discard:focus-visible,
356
- .save:focus-visible {
357
- outline: 2px solid var(--dsw-alias-brand-primary);
358
- outline-offset: 1px;
359
- }