Browse Source

fix(ui-settings-plugins): relax Subagent card layout

Dudu-0223 3 weeks ago
parent
commit
4cc1f5e0ff

+ 13 - 1
packages/client/ui-settings-plugins/src/client/PluginCard.tsx

@@ -14,7 +14,7 @@
  * disabled card the user cannot act on.
  */
 
-import { useState, type ReactNode } from 'react'
+import { useEffect, useRef, useState, type ReactNode } from 'react'
 import clsx from 'clsx'
 import { IconChevronDownOutline14 } from '@deepseek-ai/dsh-client-ui-primitives'
 import type { CardShell } from './card-form.ts'
@@ -46,7 +46,19 @@ export interface PluginCardProps {
  */
 export function PluginCard(props: PluginCardProps) {
   const [open, setOpen] = useState(false)
+  const saveStarted = useRef(false)
   const { state } = props
+  // Collapse only after Host-confirmed settlement; a rejected write keeps its
+  // diagnostics and retained drafts visible for correction.
+  useEffect(() => {
+    if (state.saving) {
+      saveStarted.current = true
+      return
+    }
+    if (!saveStarted.current) return
+    saveStarted.current = false
+    if (!state.dirty && !state.failed) setOpen(false)
+  }, [state.dirty, state.failed, state.saving])
   if (!state.available) return null
   const title = props.t(props.titleKey)
   const blocked = !state.dirty || state.invalid || state.saving

+ 13 - 1
packages/client/ui-settings-plugins/src/client/SubagentModelSelectionCard.module.css

@@ -1,12 +1,24 @@
+.permission {
+  display: grid;
+  gap: 6px;
+  padding: 12px 0;
+}
+
 .toggleRow {
   display: flex;
-  align-items: center;
+  align-items: flex-start;
   justify-content: space-between;
   gap: 16px;
   font-size: 13px;
+  line-height: 1.5;
   color: var(--dsw-alias-label-secondary);
 }
 
+.toggleLabel {
+  flex: 1;
+  min-width: 0;
+}
+
 .switch {
   box-sizing: border-box;
   position: relative;

+ 19 - 15
packages/client/ui-settings-plugins/src/client/SubagentModelSelectionCard.tsx

@@ -70,24 +70,28 @@ export function SubagentModelSelectionCard(props: SubagentModelSelectionCardProp
       onSave={props.save}
       onDiscard={props.discard}
     >
-      <div className={css.toggleRow}>
-        <span>{t('subagentModelSelectionToggle')}</span>
-        <button
-          type="button"
-          role="switch"
-          aria-checked={state.enabled}
-          aria-label={t('subagentModelSelectionToggle')}
-          className={clsx(css.switch, state.enabled && css.switchOn)}
-          disabled={!state.writable || state.saving}
-          onClick={props.toggleEnabled}
-        >
-          <span className={css.thumb} />
-        </button>
+      <div className={css.permission}>
+        <div className={css.toggleRow}>
+          <span className={css.toggleLabel}>{t('subagentModelSelectionToggle')}</span>
+          <button
+            type="button"
+            role="switch"
+            aria-checked={state.enabled}
+            aria-label={t('subagentModelSelectionToggle')}
+            className={clsx(css.switch, state.enabled && css.switchOn)}
+            disabled={!state.writable || state.saving}
+            onClick={props.toggleEnabled}
+          >
+            <span className={css.thumb} />
+          </button>
+        </div>
+        <p className={css.hint}>
+          {t(state.enabled ? 'subagentModelSelectionChoose' : 'subagentModelSelectionOff')}
+        </p>
       </div>
       {state.enabled
         ? (
           <div className={css.selection}>
-            <p className={css.hint}>{t('subagentModelSelectionChoose')}</p>
             {state.catalogStatus === 'loading'
               ? <p className={css.notice} role="status">{t('subagentModelSelectionLoading')}</p>
               : null}
@@ -130,7 +134,7 @@ export function SubagentModelSelectionCard(props: SubagentModelSelectionCardProp
             {state.invalid ? <p className={css.invalid}>{t('subagentModelSelectionRequired')}</p> : null}
           </div>
         )
-        : <p className={css.hint}>{t('subagentModelSelectionOff')}</p>}
+        : null}
     </PluginCard>
   )
 }

+ 31 - 3
packages/client/ui-settings-plugins/tests/section.client.spec.tsx

@@ -1,6 +1,6 @@
 // @vitest-environment jsdom
 
-import { cleanup, fireEvent, render, screen } from '@testing-library/react'
+import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
 import { afterEach, describe, expect, it, vi } from 'vitest'
 import { bindSnapshotSelector } from '@deepseek-ai/dsh-client-test-runtime'
 import { createSnapshotStore } from '@deepseek-ai/dsh-client-store'
@@ -69,7 +69,7 @@ function renderConfigurable(namespaces: string[], cards: Record<string, string>
   render(<ConfigurablePluginsTab {...props} />)
 }
 
-function renderBash(state: Partial<BashCardState> = {}) {
+function renderBashCard(state: Partial<BashCardState> = {}) {
   const store = createSnapshotStore<BashCardState>({
     ...settled,
     timeoutMs: field('60000'),
@@ -79,7 +79,11 @@ function renderBash(state: Partial<BashCardState> = {}) {
   const actions = cardActions()
   const props = { ...actions, t, useBashCard: bindSnapshotSelector(store) } as unknown as BashCardProps
   render(<BashCard {...props} />)
-  return actions
+  return { actions, store }
+}
+
+function renderBash(state: Partial<BashCardState> = {}) {
+  return renderBashCard(state).actions
 }
 
 function renderSubagentModelSelection(state: Partial<SubagentModelSelectionCardState> = {}) {
@@ -320,6 +324,30 @@ describe('BashCard', () => {
 
     expect(screen.queryByLabelText(en.bashTimeoutMs)).toBeNull()
   })
+
+  it('collapses after a successful save settles', () => {
+    const { actions, store } = renderBashCard({ dirty: true })
+    fireEvent.click(screen.getByText(en.bashTitle))
+    fireEvent.click(screen.getByRole('button', { name: en.save }))
+    expect(actions.save).toHaveBeenCalledOnce()
+
+    act(() => { store.set({ ...store.getSnapshot(), saving: true }) })
+    act(() => { store.set({ ...store.getSnapshot(), dirty: false, saving: false }) })
+
+    expect(screen.queryByLabelText(en.bashTimeoutMs)).toBeNull()
+  })
+
+  it('keeps a failed save open', () => {
+    const { store } = renderBashCard({ dirty: true })
+    fireEvent.click(screen.getByText(en.bashTitle))
+    fireEvent.click(screen.getByRole('button', { name: en.save }))
+
+    act(() => { store.set({ ...store.getSnapshot(), saving: true }) })
+    act(() => { store.set({ ...store.getSnapshot(), failed: true, saving: false }) })
+
+    expect(screen.getByLabelText(en.bashTimeoutMs)).toBeTruthy()
+    expect(screen.getByText(en.saveFailed)).toBeTruthy()
+  })
 })
 
 describe('SubagentModelSelectionCard', () => {