@fonlan/dsh-quick-commands 0.1.5 → 0.1.7

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.
@@ -8,20 +8,18 @@
8
8
  */
9
9
  import { useEffect, useLayoutEffect, useRef, useState } from 'react'
10
10
  import { createPortal } from 'react-dom'
11
- import {
12
- IconPlayOutline16,
13
- IconCloseOutline16,
14
- IconStopFill16,
15
- } from '@deepseek-ai/dsh-client-ui-primitives'
11
+ import { PlayIcon, CloseIcon, StopIcon } from './icons'
16
12
  import { quickApi } from './api'
17
13
  import { useRunPopover, openMenu, closeMenu, openRun, closeRun } from './run-state'
18
- import type { QuickPopupSize } from '../shared/contract'
14
+ import { anchorOf, type QuickPopupSize } from '../shared/contract'
19
15
 
20
16
  /** Workspace row shape the standard useWorkspaces hook exposes. */
21
17
  interface WorkspaceRow {
22
18
  workspaceId: string
23
19
  path: string
24
20
  title: string
21
+ /** Session membership; the canonical link DSH itself resolves Workspaces by. */
22
+ sessionIds?: string[]
25
23
  }
26
24
 
27
25
  /** Session list state subset we read (cwd + id). */
@@ -91,7 +89,12 @@ function useCurrentWorkspace(
91
89
  ): { workspace: WorkspaceRow | undefined; cwd: string | undefined } {
92
90
  const row = useSessions?.((s) => s.byId[sessionId]) as SessionRow | undefined
93
91
  const workspaces = useWorkspaces?.((s) => s.items) as WorkspaceRow[] | undefined
94
- return { workspace: matchWorkspace(row?.cwd, workspaces ?? []), cwd: row?.cwd }
92
+ const items = workspaces ?? []
93
+ // Membership first (how DSH core resolves a session's Workspace, and the only
94
+ // link that survives when a session runs outside the Workspace root), then
95
+ // the longest cwd prefix as the fallback for hosts without membership data.
96
+ const byMembership = items.find((ws) => ws.sessionIds?.includes(sessionId) === true)
97
+ return { workspace: byMembership ?? matchWorkspace(row?.cwd, items), cwd: row?.cwd }
95
98
  }
96
99
 
97
100
  /** Header icon button + command menu + live output popup. */
@@ -159,7 +162,7 @@ export function QuickCommandsHeaderAction(props: HeaderProps): JSX.Element {
159
162
  const own = entries.find((e) => e.workspaceId === workspace?.workspaceId)
160
163
  setCommands(own === undefined ? [] : own.commands)
161
164
  setMenuRemoteHost(own?.remoteHost ?? null)
162
- setAnchor(settings.popupAnchor)
165
+ setAnchor(anchorOf(settings.popupAnchor))
163
166
  setPopupSize(settings.popupSize)
164
167
  setMenuLoadError(null)
165
168
  } catch (e) {
@@ -201,7 +204,7 @@ export function QuickCommandsHeaderAction(props: HeaderProps): JSX.Element {
201
204
  disabled={workspace === undefined}
202
205
  onClick={() => { popover.menuOpen ? closeMenu() : openMenu() }}
203
206
  >
204
- <IconPlayOutline16 size={15} />
207
+ <PlayIcon size={15} />
205
208
  </button>
206
209
 
207
210
  {popover.menuOpen && workspace !== undefined && menuPosition !== null && createPortal(
@@ -216,7 +219,7 @@ export function QuickCommandsHeaderAction(props: HeaderProps): JSX.Element {
216
219
  {menuRemoteHost !== null && <span className="qc-menu-remote" title={`SSH · ${menuRemoteHost}`}>SSH</span>}
217
220
  <span className="qc-menu-ws">{workspace.title}</span>
218
221
  <button type="button" className="qc-menu-close" aria-label={t('runClose')} onClick={closeMenu}>
219
- <IconCloseOutline16 size={12} />
222
+ <CloseIcon size={12} />
220
223
  </button>
221
224
  </div>
222
225
  {commands.length === 0 && (
@@ -495,7 +498,7 @@ function RunPopup(props: {
495
498
  aria-label={t('runKill')}
496
499
  onClick={() => void kill()}
497
500
  >
498
- <IconStopFill16 size={14} />
501
+ <StopIcon size={14} />
499
502
  </button>
500
503
  <button
501
504
  type="button"
@@ -505,7 +508,7 @@ function RunPopup(props: {
505
508
  onPointerUp={onClosePointerUp}
506
509
  onClick={closeOutput}
507
510
  >
508
- <IconCloseOutline16 size={14} />
511
+ <CloseIcon size={14} />
509
512
  </button>
510
513
  </header>
511
514
 
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Icon compatibility layer over `@deepseek-ai/dsh-client-ui-primitives`.
3
+ *
4
+ * DSH renamed this package's icon exports in 0.1.7: the size-suffixed names
5
+ * (`IconPlayOutline16`) became weight-suffixed ones (`IconPlayOutlineRegular` /
6
+ * `IconPlayOutlineMedium`, matching what the official header plugins use).
7
+ *
8
+ * A client bundle that statically imports a name the host no longer exports
9
+ * receives `undefined`; rendering that element throws, the slot's per-entry
10
+ * error boundary swallows the whole entry, and the surface silently vanishes —
11
+ * exactly how the header ▶ button disappeared on 0.1.7.
12
+ *
13
+ * Resolving by name at runtime (new scheme → legacy scheme → bundled artwork)
14
+ * keeps every surface rendering across DSH alphas. The bundled artwork is only
15
+ * a last resort, so a future rename can never blank a control again.
16
+ */
17
+ import type { ReactElement, ReactNode } from 'react'
18
+ import * as primitives from '@deepseek-ai/dsh-client-ui-primitives'
19
+
20
+ /** Props both naming schemes accept (the legacy exports ignore extra fields). */
21
+ export interface IconProps {
22
+ /** Rendered square size in CSS pixels. */
23
+ size?: number
24
+ className?: string
25
+ }
26
+
27
+ type IconComponent = (props: IconProps) => ReactElement
28
+
29
+ const ICON_EXPORTS = primitives as unknown as Record<string, IconComponent | undefined>
30
+
31
+ /** First export name that exists on the running host, else the bundled artwork. */
32
+ function pickIcon(exportNames: readonly string[], fallback: IconComponent): IconComponent {
33
+ for (const name of exportNames) {
34
+ const candidate = ICON_EXPORTS[name]
35
+ if (typeof candidate === 'function') return candidate
36
+ }
37
+ return fallback
38
+ }
39
+
40
+ /** Wrap one locally drawn 16×16 artwork as an icon component. */
41
+ function localIcon(children: ReactNode): IconComponent {
42
+ return function LocalIcon({ size = 16, className }: IconProps): ReactElement {
43
+ return (
44
+ <svg
45
+ width={size}
46
+ height={size}
47
+ viewBox="0 0 16 16"
48
+ fill="none"
49
+ className={className}
50
+ aria-hidden="true"
51
+ >
52
+ {children}
53
+ </svg>
54
+ )
55
+ }
56
+ }
57
+
58
+ /** Run/play glyph for the header quick-run button. */
59
+ export const PlayIcon = pickIcon(
60
+ ['IconPlayOutlineRegular', 'IconPlayOutlineMedium', 'IconPlayOutline16'],
61
+ localIcon(
62
+ <>
63
+ <circle cx="8" cy="8" r="6.5" stroke="currentColor" />
64
+ <path d="M6.6 5.9 10.4 8l-3.8 2.1Z" fill="currentColor" />
65
+ </>,
66
+ ),
67
+ )
68
+
69
+ /** Dismiss glyph (menu header and popup close). */
70
+ export const CloseIcon = pickIcon(
71
+ ['IconCloseOutlineRegular', 'IconCloseOutlineMedium', 'IconCloseOutline16'],
72
+ localIcon(
73
+ <path d="M4.2 4.2 11.8 11.8M11.8 4.2 4.2 11.8" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />,
74
+ ),
75
+ )
76
+
77
+ /** Stop/kill glyph for a running command. */
78
+ export const StopIcon = pickIcon(
79
+ ['IconStopFillRegular', 'IconStopFillMedium', 'IconStopFill16'],
80
+ localIcon(<rect x="4.6" y="4.6" width="6.8" height="6.8" rx="1.6" fill="currentColor" />),
81
+ )
82
+
83
+ /** Add-row glyph in the settings page. */
84
+ export const PlusIcon = pickIcon(
85
+ ['IconPlusOutlineRegular', 'IconPlusOutlineMedium', 'IconPlusOutline16'],
86
+ localIcon(
87
+ <path d="M8 3.4v9.2M3.4 8h9.2" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />,
88
+ ),
89
+ )
90
+
91
+ /** Remove-command glyph in the settings page. */
92
+ export const TrashIcon = pickIcon(
93
+ ['IconTrashOutlineRegular', 'IconTrashOutlineMedium', 'IconTrashOutline16'],
94
+ localIcon(
95
+ <>
96
+ <path d="M3.4 4.4h9.2" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />
97
+ <path
98
+ d="M6.4 4.4V3.1h3.2v1.3M4.9 4.4l.6 8.5h5l.6-8.5"
99
+ stroke="currentColor"
100
+ strokeWidth="1.2"
101
+ strokeLinecap="round"
102
+ strokeLinejoin="round"
103
+ />
104
+ </>,
105
+ ),
106
+ )
@@ -2,9 +2,10 @@
2
2
  * @fonlan/dsh-quick-commands client half.
3
3
  *
4
4
  * Registers:
5
- * - `settings.plugin.item` card (keyed by the `quick-commands` settings
6
- * namespace) — per-workspace command CRUD + popup anchor preference.
7
- * - `conversation.session.header.utilities` entry (id `quick-commands`,
5
+ * - "settings.section" page (id "quick-commands", order 330) — per-workspace
6
+ * command CRUD + popup anchor preference, rendered as its own page in the
7
+ * settings sidebar.
8
+ * - "conversation.session.header.utilities" entry (id "quick-commands",
8
9
  * order -1 → left of the Session log pill) — the quick-run ▶ button, command
9
10
  * menu, and live output popup.
10
11
  */
@@ -12,9 +13,8 @@ import type { Context } from '@deepseek-ai/cordis'
12
13
  type ClientContext = Context
13
14
  import type {} from '@deepseek-ai/dsh-client-locale/client'
14
15
  import type {} from '@deepseek-ai/dsh-client-ui-conversation/client'
15
- import type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'
16
16
  import { QuickCommandsHeaderAction } from './header'
17
- import { QuickCommandsSettingsCard } from './settings-card'
17
+ import { QuickCommandsSettingsSection } from './settings-section'
18
18
  import { LOCALE_NS, zh, en } from './locales'
19
19
  import './quick-commands.css'
20
20
 
@@ -22,7 +22,7 @@ import './quick-commands.css'
22
22
  interface Slots {
23
23
  inject(name: string, callback: () => unknown): unknown
24
24
  register(
25
- def: { name: string; key?: string; id?: string; order?: number; locale?: string },
25
+ def: { name: string; id?: string; order?: number; label?: string | (() => string); locale?: string },
26
26
  component: unknown,
27
27
  ): unknown
28
28
  }
@@ -36,16 +36,17 @@ export function apply(ctx: ClientContext): void {
36
36
  const t = ctx.locale.bind(LOCALE_NS)
37
37
  const slots = (ctx as unknown as { slots: Slots }).slots
38
38
 
39
- // The plugin's own Settings Card (设置 → 插件配置) rides the `quick-commands`
40
- // settings namespace: registering into the keyed `settings.plugin.item` slot
41
- // with the namespace string makes the configurable-plugins tab dispatch the
42
- // card next to the built-in ones (bash / agent loop / web search).
43
- slots.inject('settings.plugin.item', () =>
39
+ // The plugin's own settings page (设置 → 侧栏「快捷命令」) rides the
40
+ // "quick-commands" locale namespace: a "settings.section" list entry gives it
41
+ // one row in the settings sidebar and renders the page in the content column.
42
+ slots.inject('settings.section', () =>
44
43
  slots.register({
45
- name: 'settings.plugin.item',
46
- key: LOCALE_NS,
44
+ name: 'settings.section',
45
+ id: 'quick-commands',
46
+ order: 330,
47
+ label: () => t('settingsTitle'),
47
48
  locale: LOCALE_NS,
48
- }, QuickCommandsSettingsCard as never),
49
+ }, QuickCommandsSettingsSection as never),
49
50
  )
50
51
 
51
52
  // Session-header util: order -1 renders LEFT of the Session log pill
@@ -37,10 +37,10 @@ export const zh = {
37
37
  runLost: '运行记录已失效',
38
38
  runNotFound: '工作区或命令未找到',
39
39
 
40
- // settings card
40
+ // settings page (设置 → 侧栏「快捷命令」)
41
+ settingsTitle: '快捷命令',
41
42
  cardTitle: '快速命令',
42
- cardDescription: '为每个工作区配置常用命令,会话头部一键运行',
43
- collapseOrExpand: '展开 / 收起',
43
+ sectionSub: '为每个工作区配置常用命令,会话头部一键运行',
44
44
  cardIntro: '为每个工作区配置常用命令,运行于工作区目录。支持占位符:{workspace} 工作区路径,{cwd} 会话目录,{title} 工作区标题。',
45
45
  cardWorkspaces: '工作区',
46
46
  cardNoWorkspaces: '没有工作区。请在侧边栏先添加工作区。',
@@ -54,9 +54,9 @@ export const zh = {
54
54
  cardAnchor: '输出弹窗位置',
55
55
  cardAnchorCorner: '右下角',
56
56
  cardAnchorButton: '锚定按钮下方',
57
- }
57
+ } as const
58
58
 
59
- export const en = {
59
+ export const en: Record<keyof typeof zh, string> = {
60
60
  buttonTitle: 'Quick commands',
61
61
  buttonTooltip: 'Quick commands (current workspace)',
62
62
  noWorkspace: 'This session is not in any configured workspace',
@@ -87,9 +87,9 @@ export const en = {
87
87
  runLost: 'Run record expired',
88
88
  runNotFound: 'Workspace or command not found',
89
89
 
90
+ settingsTitle: 'Quick Commands',
90
91
  cardTitle: 'Quick commands',
91
- cardDescription: 'Configure common commands per workspace, run them from the session header',
92
- collapseOrExpand: 'Expand / collapse',
92
+ sectionSub: 'Configure common commands per workspace, run them from the session header',
93
93
  cardIntro: 'Configure common commands per workspace; they run in the workspace directory. Placeholders: {workspace} workspace path, {cwd} session cwd, {title} workspace title.',
94
94
  cardWorkspaces: 'Workspaces',
95
95
  cardNoWorkspaces: 'No workspaces. Add one in the sidebar first.',
@@ -370,196 +370,6 @@
370
370
  opacity: 1;
371
371
  }
372
372
 
373
- /* ── settings card (设置 → 插件配置) — mirrors official PluginCard disclosure ── */
374
- .qc-settings-card {
375
- border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
376
- background: var(--dsw-alias-bg-layer-2, #1f1f21);
377
- border-radius: 12px;
378
- list-style: none;
379
- transition: border-color .16s, background .16s;
380
- }
381
- .qc-settings-card:hover {
382
- border-color: var(--dsw-alias-label-secondary, #cfd3d6);
383
- }
384
- .qc-settings-card-open {
385
- background: var(--dsw-alias-bg-layer-1, #27272a);
386
- border-color: var(--dsw-alias-label-secondary, #cfd3d6);
387
- }
388
- .qc-settings-header {
389
- appearance: none;
390
- width: 100%;
391
- font: inherit;
392
- color: inherit;
393
- text-align: left;
394
- cursor: pointer;
395
- background: transparent;
396
- border: 0;
397
- border-radius: 12px;
398
- align-items: center;
399
- gap: 12px;
400
- padding: 14px 16px;
401
- display: flex;
402
- }
403
- .qc-settings-header:focus-visible {
404
- outline: 2px solid var(--dsw-alias-brand-primary, #4a9eff);
405
- outline-offset: -2px;
406
- }
407
- .qc-settings-head-text {
408
- flex-direction: column;
409
- flex: 1;
410
- gap: 4px;
411
- min-width: 0;
412
- display: flex;
413
- }
414
- .qc-settings-name {
415
- color: var(--dsw-alias-label-primary, #f2f2f2);
416
- font-size: 15px;
417
- font-weight: 600;
418
- line-height: 1.4;
419
- }
420
- .qc-settings-description {
421
- color: var(--dsw-alias-label-secondary, #cfd3d6);
422
- font-size: 13px;
423
- line-height: 1.5;
424
- }
425
- .qc-settings-chevron {
426
- color: var(--dsw-alias-label-secondary, #cfd3d6);
427
- flex: none;
428
- transition: transform .16s;
429
- }
430
- .qc-settings-chevron-open {
431
- transform: rotate(180deg);
432
- }
433
- .qc-settings-body {
434
- border-top: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
435
- margin: 0 16px;
436
- padding-bottom: 12px;
437
- display: flex;
438
- flex-direction: column;
439
- gap: 10px;
440
- }
441
- .qc-settings-intro {
442
- font-size: 12px;
443
- color: var(--dsw-alias-label-secondary, #cfd3d6);
444
- margin: 8px 0 0;
445
- line-height: 1.5;
446
- }
447
- .qc-settings-anchor {
448
- display: flex;
449
- align-items: center;
450
- gap: 12px;
451
- font-size: 12px;
452
- }
453
- .qc-settings-anchor-label {
454
- color: var(--dsw-alias-label-primary, #f2f2f2);
455
- font-weight: 500;
456
- }
457
- .qc-settings-radio {
458
- display: inline-flex;
459
- align-items: center;
460
- gap: 4px;
461
- color: var(--dsw-alias-label-secondary, #cfd3d6);
462
- cursor: pointer;
463
- }
464
- .qc-settings-error {
465
- font-size: 12px;
466
- color: var(--dsw-alias-state-error-primary, #e5484d);
467
- }
468
- .qc-settings-empty {
469
- font-size: 12px;
470
- color: var(--dsw-alias-label-secondary, #cfd3d6);
471
- margin: 0;
472
- }
473
- .qc-settings-workspace {
474
- border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
475
- border-radius: 10px;
476
- padding: 10px;
477
- display: flex;
478
- flex-direction: column;
479
- gap: 8px;
480
- }
481
- .qc-settings-workspace-head {
482
- display: flex;
483
- align-items: baseline;
484
- gap: 8px;
485
- }
486
- .qc-settings-workspace-title {
487
- font-size: 13px;
488
- font-weight: 600;
489
- color: var(--dsw-alias-label-primary, #f2f2f2);
490
- }
491
- .qc-settings-workspace-path {
492
- font-size: 11px;
493
- font-family: var(--dsw-font-family-mono, monospace);
494
- color: var(--dsw-alias-label-tertiary, #adb2b8);
495
- overflow: hidden;
496
- text-overflow: ellipsis;
497
- white-space: nowrap;
498
- }
499
- .qc-settings-no-commands {
500
- font-size: 12px;
501
- color: var(--dsw-alias-label-tertiary, #adb2b8);
502
- margin: 0;
503
- }
504
- .qc-settings-command {
505
- display: flex;
506
- gap: 6px;
507
- align-items: center;
508
- }
509
- .qc-settings-field {
510
- border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
511
- border-radius: 8px;
512
- background: var(--dsw-alias-bg-layer-1, #2a2a2d);
513
- color: var(--dsw-alias-label-primary, #f2f2f2);
514
- font-size: 12px;
515
- padding: 6px 8px;
516
- outline: none;
517
- }
518
- .qc-settings-field::placeholder {
519
- color: var(--dsw-alias-label-tertiary, #adb2b8);
520
- }
521
- .qc-settings-field:focus {
522
- border-color: var(--dsw-alias-brand-primary, #4a9eff);
523
- }
524
- .qc-settings-field-name {
525
- width: 160px;
526
- flex: none;
527
- }
528
- .qc-settings-field-command {
529
- flex: 1;
530
- font-family: var(--dsw-font-family-mono, monospace);
531
- }
532
- .qc-settings-icon-btn {
533
- border: 0;
534
- background: transparent;
535
- color: var(--dsw-alias-label-tertiary, #adb2b8);
536
- cursor: pointer;
537
- padding: 4px;
538
- display: inline-flex;
539
- align-items: center;
540
- border-radius: 6px;
541
- }
542
- .qc-settings-icon-btn:hover {
543
- background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);
544
- color: var(--dsw-alias-state-error-primary, #e5484d);
545
- }
546
- .qc-settings-add-btn {
547
- align-self: flex-start;
548
- display: inline-flex;
549
- align-items: center;
550
- gap: 4px;
551
- border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
552
- border-radius: 8px;
553
- background: transparent;
554
- color: var(--dsw-alias-label-primary, #f2f2f2);
555
- font-size: 12px;
556
- padding: 4px 10px;
557
- cursor: pointer;
558
- }
559
- .qc-settings-add-btn:hover {
560
- background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);
561
- }
562
-
563
373
  /* ── mobile fallback (dsh-mobile-nav) ──────────────────────────────────────
564
374
  The dsh-mobile-nav plugin (≤1023px) hides the ENTIRE header utilities seat
565
375
  with `[data-mobile-nav="frame"] [data-phase] header > :first-child >
@@ -605,4 +415,4 @@
605
415
  min-height: 44px;
606
416
  justify-content: center;
607
417
  }
608
- }
418
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Styles for the quick-commands settings page.
3
+ *
4
+ * The page occupies the settings panel content column: a static header
5
+ * (title plus a one-line summary) over the always-visible command editor.
6
+ * Inner controls follow the built-in settings language.
7
+ */
8
+
9
+ /* page layout */
10
+
11
+ .qc-page {
12
+ display: flex;
13
+ flex-direction: column;
14
+ gap: 16px;
15
+ max-width: 720px;
16
+ }
17
+ .qc-page-head {
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: 6px;
21
+ }
22
+ .qc-page-title {
23
+ margin: 0;
24
+ color: var(--dsw-alias-label-primary, #f2f2f2);
25
+ font-size: 15px;
26
+ font-weight: 600;
27
+ line-height: 1.4;
28
+ }
29
+ .qc-page-sub {
30
+ margin: 0;
31
+ color: var(--dsw-alias-label-secondary, #cfd3d6);
32
+ font-size: 13px;
33
+ line-height: 1.5;
34
+ }
35
+
36
+ /* body: workspace / command-set editor */
37
+
38
+ .qc-settings-body {
39
+ display: flex;
40
+ flex-direction: column;
41
+ gap: 10px;
42
+ }
43
+ .qc-settings-intro {
44
+ font-size: 12px;
45
+ color: var(--dsw-alias-label-secondary, #cfd3d6);
46
+ margin: 0;
47
+ line-height: 1.5;
48
+ }
49
+ .qc-settings-anchor {
50
+ display: flex;
51
+ align-items: center;
52
+ gap: 12px;
53
+ font-size: 12px;
54
+ }
55
+ .qc-settings-anchor-label {
56
+ color: var(--dsw-alias-label-primary, #f2f2f2);
57
+ font-weight: 500;
58
+ }
59
+ .qc-settings-radio {
60
+ display: inline-flex;
61
+ align-items: center;
62
+ gap: 4px;
63
+ color: var(--dsw-alias-label-secondary, #cfd3d6);
64
+ cursor: pointer;
65
+ }
66
+ .qc-settings-error {
67
+ font-size: 12px;
68
+ color: var(--dsw-alias-state-error-primary, #e5484d);
69
+ }
70
+ .qc-settings-empty {
71
+ font-size: 12px;
72
+ color: var(--dsw-alias-label-secondary, #cfd3d6);
73
+ margin: 0;
74
+ }
75
+ .qc-settings-workspace {
76
+ border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
77
+ border-radius: 10px;
78
+ padding: 10px;
79
+ display: flex;
80
+ flex-direction: column;
81
+ gap: 8px;
82
+ }
83
+ .qc-settings-workspace-head {
84
+ display: flex;
85
+ align-items: baseline;
86
+ gap: 8px;
87
+ }
88
+ .qc-settings-workspace-title {
89
+ font-size: 13px;
90
+ font-weight: 600;
91
+ color: var(--dsw-alias-label-primary, #f2f2f2);
92
+ }
93
+ .qc-settings-workspace-path {
94
+ font-size: 11px;
95
+ font-family: var(--dsw-font-family-mono, monospace);
96
+ color: var(--dsw-alias-label-tertiary, #adb2b8);
97
+ overflow: hidden;
98
+ text-overflow: ellipsis;
99
+ white-space: nowrap;
100
+ }
101
+ .qc-settings-no-commands {
102
+ font-size: 12px;
103
+ color: var(--dsw-alias-label-tertiary, #adb2b8);
104
+ margin: 0;
105
+ }
106
+ .qc-settings-command {
107
+ display: flex;
108
+ gap: 6px;
109
+ align-items: center;
110
+ }
111
+ .qc-settings-field {
112
+ border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
113
+ border-radius: 8px;
114
+ background: var(--dsw-alias-bg-layer-1, #2a2a2d);
115
+ color: var(--dsw-alias-label-primary, #f2f2f2);
116
+ font-size: 12px;
117
+ padding: 6px 8px;
118
+ outline: none;
119
+ }
120
+ .qc-settings-field::placeholder {
121
+ color: var(--dsw-alias-label-tertiary, #adb2b8);
122
+ }
123
+ .qc-settings-field:focus {
124
+ border-color: var(--dsw-alias-brand-primary, #4a9eff);
125
+ }
126
+ .qc-settings-field-name {
127
+ width: 160px;
128
+ flex: none;
129
+ }
130
+ .qc-settings-field-command {
131
+ flex: 1;
132
+ font-family: var(--dsw-font-family-mono, monospace);
133
+ }
134
+ .qc-settings-icon-btn {
135
+ border: 0;
136
+ background: transparent;
137
+ color: var(--dsw-alias-label-tertiary, #adb2b8);
138
+ cursor: pointer;
139
+ padding: 4px;
140
+ display: inline-flex;
141
+ align-items: center;
142
+ border-radius: 6px;
143
+ }
144
+ .qc-settings-icon-btn:hover {
145
+ background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);
146
+ color: var(--dsw-alias-state-error-primary, #e5484d);
147
+ }
148
+ .qc-settings-add-btn {
149
+ align-self: flex-start;
150
+ display: inline-flex;
151
+ align-items: center;
152
+ gap: 4px;
153
+ border: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.35));
154
+ border-radius: 8px;
155
+ background: transparent;
156
+ color: var(--dsw-alias-label-primary, #f2f2f2);
157
+ font-size: 12px;
158
+ padding: 4px 10px;
159
+ cursor: pointer;
160
+ }
161
+ .qc-settings-add-btn:hover {
162
+ background: color-mix(in srgb, var(--dsw-alias-label-primary, #f2f2f2) 10%, transparent);
163
+ }