Bläddra i källkod

fix(web): trash icon for preset row deletion, drop the not-mounted line

The Delete on a row the person added to a preset is a red trash icon with
the same accessible name. The plugin card no longer says whether a running
session has the plugin loaded under the preset scope: the Host's registered
flag cannot tell "the preset does not compose it" from "no session runs",
so the line said nothing a person could act on. The unread shell field goes
with it; the README states when saved values apply instead.
Yichen Jiang 1 månad sedan
förälder
incheckning
0c40a810cb

+ 2 - 1
apps/web/tests/expected/plugin-manager/preset-detail.expected.md

@@ -93,7 +93,8 @@
     - switch "启用 网页" [checked]
   - listitem:
     - text: "示例插件 本地 Web e2e fixture: a plain plugin module with no bundle, added to a composition per row."
-    - button "从这个预设删除 示例插件": 删除
+    - button "从这个预设删除 示例插件":
+      - img
     - switch "启用 示例插件" [checked]
 - heading "设置" [level=3]
 - list:

+ 5 - 4
packages/client/ui-settings-plugin-manager/src/client/PluginManagerSettingsTab.module.css

@@ -368,14 +368,15 @@
 
 /* The delete of a row the person added: red on its own, not only on hover. */
 .deleteButton {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  width: 28px;
+  height: 28px;
   border: 0;
   border-radius: 8px;
-  padding: 4px 8px;
   background: transparent;
   color: var(--dsw-alias-state-error-primary);
-  font: inherit;
-  font-size: 12.5px;
-  line-height: 18px;
   cursor: pointer;
 }
 

+ 3 - 2
packages/client/ui-settings-plugin-manager/src/client/PresetPluginsSection.tsx

@@ -10,7 +10,7 @@
 import { useEffect, useState, type ReactNode } from 'react'
 import clsx from 'clsx'
 import type { PluginPackageView } from '@deepseek-ai/dsh-api-remotes/client'
-import { Menu, type MenuItem } from '@deepseek-ai/dsh-client-ui-primitives'
+import { IconTrashOutline16, Menu, type MenuItem } from '@deepseek-ai/dsh-client-ui-primitives'
 import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
 // Type-only: the detail slot's declaration, owned by the roster.
 import type {} from '@deepseek-ai/dsh-client-ui-agent-preset/client'
@@ -87,10 +87,11 @@ function PresetCard({ preset, row, packages, t, busy, onSetDisabled, onRemove }:
                       type="button"
                       className={css.deleteButton}
                       aria-label={t('rowRemoveLabel', { name: copy.title })}
+                      title={t('rowRemove')}
                       disabled={busy}
                       onClick={() => { onRemove(id) }}
                     >
-                      {t('rowRemove')}
+                      <IconTrashOutline16 />
                     </button>
                   )
                   : null}

+ 2 - 2
packages/client/ui-settings-plugins/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/client/ui-settings-plugins/README.md
-README.md: bff9626a1ef98ccecb95ede09105a11109a18ae3
-README.zh.md: 9d805b5cf35946c87f790458fccb41d8cc301289
+README.md: ed91398dffd192e5cc15ff6d77f3175b8c2ace70
+README.zh.md: d9237b0429ac05e36b7c61e024d23e2d908626e4

+ 1 - 1
packages/client/ui-settings-plugins/README.md

@@ -29,7 +29,7 @@ Open the Plugins section in Settings and select the **Plugin configuration** tab
 
 ### A preset's own values
 
-A preset's detail page — behind the gear on its card in the Agent presets section — carries a **Settings** section with the same cards, editing that preset's named scope (`preset/<id>`). There a field the preset does not override shows **Inherited** when the shared user layer carries it, a reset stages the inherited value rather than the composition default, and a card whose plugin no running session of the preset has loaded says so — its values are stored and apply to the sessions that load the plugin. Drafts belong to the scope they were typed under and survive leaving the page. The configuration tab always edits the shared values.
+A preset's detail page — behind the gear on its card in the Agent presets section — carries a **Settings** section with the same cards, editing that preset's named scope (`preset/<id>`). There a field the preset does not override shows **Inherited** when the shared user layer carries it, and a reset stages the inherited value rather than the composition default. Saved values land in the preset's own section: a running session composed from the preset picks them up at once, and later sessions read them when they start. Drafts belong to the scope they were typed under and survive leaving the page. The configuration tab always edits the shared values.
 
 ### What appears here
 

+ 1 - 1
packages/client/ui-settings-plugins/README.zh.md

@@ -29,7 +29,7 @@ kind: "package-reference"
 
 ### 预设自己的值
 
-预设的详情页——Agent 预设分区里卡片上的齿轮之后——带一个**设置**段,用同样的卡片编辑该预设的命名作用域(`preset/<id>`)。在那里,预设未覆盖的字段在共用用户层持有它时显示**继承**,重置暂存的是继承值而非组装默认值,而所属插件没有被该预设任何运行中会话加载的卡片会如此说明——其值照样存储,对加载该插件的会话生效。草稿属于输入时所在的作用域,离开页面后仍在。配置标签页始终编辑共用值。
+预设的详情页——Agent 预设分区里卡片上的齿轮之后——带一个**设置**段,用同样的卡片编辑该预设的命名作用域(`preset/<id>`)。在那里,预设未覆盖的字段在共用用户层持有它时显示**继承**,重置暂存的是继承值而非组装默认值。保存的值写进该预设自己的段:由该预设组装的运行中会话立即读到,之后的会话在启动时读取。草稿属于输入时所在的作用域,离开页面后仍在。配置标签页始终编辑共用值。
 
 ### 这里会出现什么
 

+ 0 - 3
packages/client/ui-settings-plugins/src/client/PluginCard.tsx

@@ -82,9 +82,6 @@ export function PluginCard(props: PluginCardProps) {
         ? (
           <div className={css.body}>
             {!state.writable ? <p className={css.readOnly} role="status">{props.t('readOnly')}</p> : null}
-            {state.scope !== undefined && !state.registered
-              ? <p className={css.readOnly} role="status">{props.t('notMounted')}</p>
-              : null}
             {props.children}
             <div className={css.footer}>
               {state.failed ? <p className={css.failed} role="status">{props.t('saveFailed')}</p> : null}

+ 0 - 7
packages/client/ui-settings-plugins/src/client/card-form.ts

@@ -72,12 +72,6 @@ export interface CardShell {
   available: boolean
   /** The named scope the form edits, such as `preset/<id>`; undefined for the global instance. */
   scope: string | undefined
-  /**
-   * Whether a live Host plugin registered the namespace under the form's
-   * scope. False under a named scope whose composition does not mount the
-   * plugin: edits are stored and take effect once one does.
-   */
-  registered: boolean
   /** Whether the Host document accepts writes. */
   writable: boolean
   /** Whether the form holds edits that a save would write. */
@@ -247,7 +241,6 @@ export class CardForm<T> {
     return {
       available: snapshot.status === 'ready',
       scope: snapshot.scope,
-      registered: snapshot.registered,
       writable: snapshot.writable,
       dirty: plan.length > 0,
       invalid: plan.some(item => item.run === undefined),

+ 1 - 3
packages/client/ui-settings-plugins/src/client/locales.ts

@@ -2,7 +2,7 @@
 
 /** Locale keys these surfaces render. */
 export type PluginsSettingsLocaleKey =
-  | 'nav' | 'title' | 'intro' | 'tabs' | 'configurableTab' | 'empty' | 'presetSettingsTitle' | 'notMounted'
+  | 'nav' | 'title' | 'intro' | 'tabs' | 'configurableTab' | 'empty' | 'presetSettingsTitle'
   | 'overridden' | 'inherited' | 'reset' | 'readOnly' | 'expand' | 'collapse'
   | 'save' | 'saving' | 'discard' | 'unsaved' | 'saveFailed' | 'invalidNumber'
   | 'bashTitle' | 'bashDescription' | 'bashTimeoutMs' | 'bashTimeoutMsHint'
@@ -29,7 +29,6 @@ export const en: Record<PluginsSettingsLocaleKey, string> = {
   configurableTab: 'Plugin configuration',
   empty: 'This deployment exposes no plugin settings.',
   presetSettingsTitle: 'Settings',
-  notMounted: 'No running session composed from this preset has this plugin loaded; saved values apply to the sessions that load it.',
   overridden: 'Overridden',
   inherited: 'Inherited',
   reset: 'Reset to default',
@@ -92,7 +91,6 @@ export const zh: Record<PluginsSettingsLocaleKey, string> = {
   configurableTab: '插件配置',
   empty: '本部署没有开放任何插件设置。',
   presetSettingsTitle: '设置',
-  notMounted: '本预设当前没有运行中的会话加载这个插件;保存的值会在加载它的会话中生效。',
   overridden: '已覆盖',
   inherited: '继承',
   reset: '恢复默认',

+ 0 - 1
packages/client/ui-settings-plugins/src/client/subagent-model-selection-card-controller.ts

@@ -362,7 +362,6 @@ export class SubagentModelSelectionCardController {
     return {
       available: snapshot.status === 'ready',
       scope: snapshot.scope,
-      registered: snapshot.registered,
       writable: snapshot.writable,
       dirty: this.currentEnabled() !== enabled || !sameRoutes(current, desired),
       invalid: enabled && desired.length === 0,

+ 3 - 3
packages/client/ui-settings-plugins/tests/apply.client.spec.ts

@@ -242,13 +242,13 @@ describe('ui-settings-plugins apply', () => {
     const bashFace = (bash.inject as unknown as () => BashCardFace)()
     const tabBash = slots.entries('settings.plugin.item')[0]!
     expect((tabBash.inject as unknown as () => BashCardFace)().hooks.bashCard).toBe(bashFace.hooks.bashCard)
-    await vi.waitFor(() => { expect(bashFace.hooks.bashCard.getSnapshot()).toMatchObject({ scope: undefined, registered: true }) })
+    await vi.waitFor(() => { expect(bashFace.hooks.bashCard.getSnapshot()).toMatchObject({ scope: undefined }) })
     describeSettings.mockClear()
     face.selectScope('preset/standard')
     // The scoped mirror is read on first selection, and the card follows it.
     await vi.waitFor(() => { expect(describeSettings).toHaveBeenCalledWith('preset/standard') })
     await vi.waitFor(() => {
-      expect(bashFace.hooks.bashCard.getSnapshot()).toMatchObject({ scope: 'preset/standard', registered: false })
+      expect(bashFace.hooks.bashCard.getSnapshot()).toMatchObject({ scope: 'preset/standard' })
     })
     // A scoped commit reloads the scoped mirror only.
     describeSettings.mockClear()
@@ -257,7 +257,7 @@ describe('ui-settings-plugins apply', () => {
     expect(describeSettings).toHaveBeenCalledWith('preset/standard')
     // Leaving the page returns every card to the global instance.
     face.selectScope(null)
-    expect(bashFace.hooks.bashCard.getSnapshot()).toMatchObject({ scope: undefined, registered: true })
+    expect(bashFace.hooks.bashCard.getSnapshot()).toMatchObject({ scope: undefined })
   })
 
   it('registers into a declaration that arrives after apply', async () => {

+ 0 - 4
packages/client/ui-settings-plugins/tests/section.client.spec.tsx

@@ -37,7 +37,6 @@ const t = (key: keyof typeof en, params?: Record<string, string>) =>
 const settled: CardShell = {
   available: true,
   scope: undefined,
-  registered: true,
   writable: true,
   dirty: false,
   invalid: false,
@@ -275,7 +274,6 @@ describe('SkillFilesystemCard', () => {
   it('edits the roots as one entry per line and marks an inherited value', () => {
     const actions = renderSkillFilesystemCard({
       scope: 'preset/research',
-      registered: false,
       customSkillDirs: field('/a\n/b', { inherited: true }),
     })
     fireEvent.click(screen.getByRole('button', { name: `${en.expand}: ${en.skillFilesystemTitle}` }))
@@ -284,7 +282,6 @@ describe('SkillFilesystemCard', () => {
     expect(roots.value).toBe('/a\n/b')
     expect(screen.getByText(en.inherited)).toBeTruthy()
     expect(screen.queryByRole('button', { name: en.reset })).toBeNull()
-    expect(screen.getByText(en.notMounted)).toBeTruthy()
     fireEvent.change(roots, { target: { value: '/c' } })
     expect(actions.edit).toHaveBeenCalledWith('customSkillDirs', '/c')
   })
@@ -296,7 +293,6 @@ describe('SkillFilesystemCard', () => {
     fireEvent.click(screen.getByRole('button', { name: en.reset }))
     expect(actions.resetField).toHaveBeenCalledWith('customSkillDirs')
     expect(screen.queryByText(en.inherited)).toBeNull()
-    expect(screen.queryByText(en.notMounted)).toBeNull()
   })
 
   it('marks an invalid root draft on the text area', () => {

+ 2 - 2
packages/client/ui-settings-plugins/tests/stores.client.spec.ts

@@ -1077,7 +1077,7 @@ describe('CardForm lines field', () => {
       user: { timeoutMs: 9000 }, revision: 1,
     })
     const subject = new CardForm(host.scope, [numberField('timeoutMs')])
-    expect(subject.shell()).toMatchObject({ scope: 'preset/research', registered: true })
+    expect(subject.shell()).toMatchObject({ scope: 'preset/research' })
     expect(subject.hasOverride('timeoutMs')).toBe(true)
     subject.actions().resetField('timeoutMs')
     expect(subject.field('timeoutMs')).toEqual({ text: '12000', overridden: false, inherited: false, invalid: false })
@@ -1245,7 +1245,7 @@ describe('SubagentModelSelectionCardController across scopes', () => {
     face.toggleEnabled()
     expect(state()).toMatchObject({ dirty: true, enabled: true, scope: undefined })
     selection.select('preset/research')
-    expect(state()).toMatchObject({ dirty: false, enabled: false, scope: 'preset/research', registered: true })
+    expect(state()).toMatchObject({ dirty: false, enabled: false, scope: 'preset/research' })
     // A save on the new scope writes that scope's instance.
     face.toggleEnabled()
     expect(state().dirty).toBe(true)