npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dsh-settings-ui

v0.4.1

Published

DSH web plugin: unified settings-page UI kit. Provides the ctx.settingsUi client service — official-aligned card/field primitives (pointer-reusing @deepseek-ai/dsh-client-ui-primitives icons + --dsw-* tokens), a settings store, and a declarative form — so

Downloads

495

Readme

English | 中文

dsh-settings-ui

完整使用与开发手册见 GUIDE.zh.md

DSH Web 插件:统一设置页 UI kit + 浮层面板 kit。对外暴露 ctx.settingsUi 客户端服务,让其它插件用一套统一样式(对齐 dsh-better-sidebar 风格、--dsw-* 语义 token)接入设置页与 shell.overlay 浮层,不用再各自手写 UI 组件、CSS 和「加载/保存/busy/错误/已保存/revision 冲突」状态逻辑

样式 token:所有 var(--dsw-alias-*, fallback) 的备用色均按官方暗色主题实值对齐(v0.2.6);在官方壳内这些 fallback 恒被真实 token 覆盖,仅在脱离官方壳时兜底。共享样式类名 sui- 前缀,与官方 --dsw-* / better-sidebar 的 --dsh-sidebar-width 命名空间互不冲突。

定位与向后兼容

  • 本 kit 只新增一个服务settingsUi),不接管、不过滤 settings.section / shell.overlay 槽。
  • 原有直连方式完全保留:插件继续用 ctx.slots.inject('settings.section', ...) 也照常工作(无需迁移)。
  • 想用新方式的插件,inject 里加 settingsUi,改用 ctx.settingsUi.section(...) / ctx.settingsUi.overlay(...) 即可。

兼容性声明

  • 已验证:dsh 0.1.0-rc.5(官方壳全套,desktop / desktop-dev profile 实测,含 framework-only bundles 挂载)。
  • rc.6(2026-08-17 已验证):kit 0.2.18 tarball 在 rc.6 双链路实测通过——桌面端(rc6-min profile,13648)+ web 端(rc6-web profile,3090)bundles 直挂:源码同 commit 铁证(上游 master = rc6 npm 构建提交 = rc5 checkout HEAD,47f9438)+ 契约面核对一致(React 18.3.1 / --dsw-alias-* / slots ledger / installLocale / locale.register)+ 冷启动零错误 + bundle 实核 + UI 手测通过。rc.6 下 link: 开发挂载 ESM 解析失败,必须 tarball。
  • rc.7(0.3.0,2026-08-20)pluginCard() 面向 rc7 的 keyed settings.plugin.item 槽 + 官方 ctx.settingsScope(保存即生效、revision 栅栏由官方保证)。经典面(settings.section / settings.general.item / shell.overlay)在 rc7 未变,section()/overlay() 照常工作。pluginCard() 是 rc7 时代的路径,新品配置卡建议走它。
  • 未列出的版本组合未验证,不声明兼容。

三级 API 速选(便捷 vs 自由)

kit 是「通用 UI 注册入口」:同一套原子组件 + sui-* 样式 + 状态,按诉求分三级用——

| 诉求 | 用哪个 | 一句话 | |---|---|---| | 在官方「插件配置」Tab放一张配置卡(rc7 范式,经 settingsScope 保存) | ui.pluginCard(config)rc7 便捷) | 声明 { key, header?, fields?, content?, showIn?, chrome? },keyed settings.plugin.item + 官方 scope 后端,kit 渲染卡壳 | | 在设置页加一张配置卡片(传统/rc6 回退) | ui.section(config)便捷) | 声明 { id, order, label, render, inject? },自动包 .sui-root 根 + 共享样式 + 统计卡计数 | | 在窗口最前层级开自己的浮窗(可拖拽 / 最小化 / 置顶 / 位置持久化) | ui.overlay(config) + ui.Panel + ui.createPanelStore自由) | 注册 shell.overlay 浮层;Panel 给 chrome;createPanelStore({ persist }) 管开合/位置/置顶 |

  • rc7 便捷 = 官方「插件配置」Tab 卡,保存由官方 settingsScope 承担(save-as-you-go、revision 栅栏)。
  • 便捷 = 设置页卡片,适合「配置类」插件(proxy / mcp / search / skill 的设置卡)。
  • 自由 = 任意浮窗,适合「伴随式」插件(桌面助手 / 会话伴侣 / 搜索面板),位置与最小化可 persist 到 localStorage。
  • 三级共享原子组件(SectionHeader / Field / Card / Button / Switch / Rows …)与 sui-* 样式;h = React.createElement
  • 需要「加载/保存/busy/error/revision」就用 ui.createSettingsStore + ui.useSettings(见 §3)。

rc7 便捷最小骨架(官方插件配置 Tab 卡):

const card = ui.pluginCard({
  key: 'my-plugin',                 // 必填:settings 命名空间 = Tab 派发键
  header: { title: '我的插件', desc: '一句话说明' },
  fields: [
    { key: 'enabled', type: 'switch', label: '启用' },
    { key: 'endpoint', type: 'text', label: '服务地址' },
  ],
})
// card.store(settingsScope 后端):每个字段编辑即经 scope.set/unset 持久化

设置页便捷最小骨架section())见下方「快速示例(完整插件)」。

自由最小骨架(浮窗):

function MyWindow(props) {
  const { ui, panel } = props
  return ui.h(ui.Panel, { title: '我的窗口', panel },
    ui.h(ui.Card, {}, '内容……'),
  )
}
const plugin = {
  inject: ['slots', 'settingsUi'],
  apply(ctx) {
    const ui = ctx.settingsUi
    const panel = ui.createPanelStore({ persist: 'my.window.v1' })  // apply 里建一次
    ui.overlay({ id: 'my-window', order: 100, inject: () => ({ ui, panel }), render: MyWindow })
  },
}

安装(挂进 profile)

与普通客户端插件一致:单包、cordis.patch.yml 单行装配、dsh.client 声明、提交 lib/。本包声明了 dsh.bundle.patch,是标准 bundle。官方 npm 安装(首选)——一步完成「加依赖 + reconcile 追加进 dsh.profile.bundles」:

dsh plugin --profile <profile-name> add dsh-settings-ui

npm latest 目前 0.2.22;0.4.0 发布后同命令升级dsh plugin --profile <profile-name> add dsh-settings-ui@latest)。⚠️ 发布后 24h 内安装会撞 pnpm v11 minimumReleaseAge 供应链冷却期、静默回退旧版——在 profile 的 pnpm-workspace.yamlminimumReleaseAge: 0,或等满 24h 再装。

本地开发(可选,二选一):npm pack 出 tgz 后 dsh plugin --profile <profile-name> add ./dsh-settings-ui-<ver>.tgz,或手写 profile:

  1. 在 profile 的 package.json 加依赖(file: 挂载 tgz;⚠️ rc.6 起 link: 会因 ESM 解析失败,一律用 tarball)。
  2. dsh.profile.bundles 里加一行 "dsh-settings-ui"
  3. pnpm install,然后硬刷新页面(client 改动无需重启)。

示例(desktop profile):

{
  "dependencies": { "dsh-settings-ui": "file:./dsh-settings-ui-<ver>.tgz" },
  "dsh": { "profile": { "bundles": ["dsh-base", "dsh-web-app", "dsh-settings-ui", "..."] } }
}

API 参考

所有能力都挂在 ctx.settingsUi 上。消费插件只需 inject: ['settingsUi'],然后在 apply(ctx) 里取 const ui = ctx.settingsUi

1. 原子组件(统一主题,共享样式只注入一次)

| 组件 | 用途 | 主要 props | |---|---|---| | ui.SectionHeader | 标题 + 一句话描述 | { title, desc } | | ui.Field | 标签 + 控件 + 提示竖排 | { label, hint, children } | | ui.TextInput | 单行输入 | { value, onChange, placeholder, type, disabled?, autoFocus?, onKeyDown?, min?, max? } | | ui.TextArea | 多行输入(等宽字体) | { value, onChange, placeholder, rows, onKeyDown?, disabled? } | | ui.Select | 下拉 | { value, onChange, disabled?, children } | | ui.Button | 按钮 | { kind: 'primary'\|'secondary'\|'danger', disabled, onClick, children } | | ui.Switch | 开关 | { checked, onChange, disabled, label, title? } | | ui.Checkbox | 复选框 | { checked, onChange, disabled?, label? }(原生 input + 内联文案) | | ui.Radio | 单选框 | { checked, onChange, disabled?, label?, name?, value? }(onChange 收 value) | | ui.Card | 卡片容器 | { row?, children }row = 横向行卡) | | ui.StatusDot | 状态点 + 文案 | { color, text, extra } | | ui.Badge | 圆角徽标 | { children, tone?, outline? }(tone: info/success/warn/error/neutral) | | ui.Spinner | 加载旋转圈 | { size?, style? } | | ui.List / ui.ListItem | 结构化行列表 | List { children }ListItem { children, onClick?, title? } | | ui.Dialog | 模态对话框 | { open, title, onClose?, footer?, children, width? }(ESC 关闭 / Tab 焦点陷阱 / aria-modal / 关闭后焦点归还) | | ui.ErrorBoundary | 错误边界 | { title?, fallback?, onError?, children }——子组件崩溃渲染错误横幅,不白屏;把手/入口放边界外 | | ui.Tabs | 下划线标签页 | { items: [{id,label,badge?}], active, onChange }role=tablist + 方向键导航) | | ui.Banner | 横幅 | { kind: 'error'\|'saved'\|'warn', children } | | ui.EmptyState | 空态占位 | { text?, children } | | ui.toast / ui.ToastHost / ui.useToast | 一次性通知 | ui.toast(text, { kind?, ttlMs? }) 广播到已挂载的 ToastHost(每插件根挂一个) | | ui.h | React.createElement 别名 | (type, props, ...children) |

2. 声明式行渲染 ui.Rows

ui.Rows({
  fields: [
    { key: 'enabled', type: 'switch', label: '启用' },
    { key: 'apiKey', type: 'text', label: 'API Key', placeholder: 'sk-...', hint: '已设置则留空保持不变' },
    { key: 'timeoutMs', type: 'number', label: '超时(ms)' },
    { key: 'transport', type: 'select', label: '传输方式', options: [{ value: 'stdio', label: 'stdio' }, { value: 'http', label: 'http' }] },
    { key: 'args', type: 'textarea', label: '参数', rows: 4 },
  ],
  values: doc,                      // 当前表单对象
  onChange: (key, value) => patchDoc({ [key]: value }),
})

type 缺省为 textswitchSwitchtextareaTextAreaselectSelect,其余走 TextInput

3. 设置状态 ui.createSettingsStore + ui.useSettings

统一「加载/保存/busy/error/saved/revision 冲突」:

const store = ui.createSettingsStore({ get: () => call('get'), update: (p) => call('update', p) }, { savedTtlMs: 3000 })
// store.refresh() / store.commit(payload) / store.run(asyncFn) / store.get()/subscribe()

function MySection(props) {
  const s = ui.useSettings(store)   // { doc, revision, busy, error, saved, loaded, dirty }
  // s.doc 表单、s.error 错误、s.busy 禁用按钮、s.dirty 未保存更改、store.commit(...) 保存
}
  • refresh():调 get() 载入 doc(若返回对象含 revision,自动提取);成功清 dirty
  • commit(payload)busy 期间调 update(payload),成功后 refresh() 并闪现 savedsavedTtlMs 后自清,默认 3000ms);返回 update() 结果(undefined → true,兼容旧布尔用法);settings-conflict 会自动重载后报错。
  • run(fn):对「增/删/启停」这类动作做同样包裹(busy + 错误 + 成功后刷新);返回 fn() 结果(undefined → true)。
  • dirty:任何 set({ doc }) 表单编辑置 truerefresh()/成功写入后归 false——离开确认/未保存提示直接读 s.dirty

4. 注册 ui.section(config)

ui.section({
  id: 'my-plugin',                 // settings.section 的 id(导航键)
  order: 200,                      // 导航位置
  label: () => '我的设置',          // 导航文案(字符串或函数)
  inject: () => ({ api: { get: () => call('get'), update: (p) => call('update', p) } }),
  render: MySection,               // React 组件,接收 compose 后的 props(含 inject 返回的 face)
})

render组件(内部可调 ui.useSettings 等 hook),section() 会包上统一的 .sui-root 根并注入共享样式。

4.5 注册官方「插件配置」Tab 卡 ui.pluginCard(config)(rc7,v0.3.0)

rc7 官方教程 adding-a-settings-card 的新范式 = 插件配置 Tab 卡(keyed settings.plugin.item 按 settings 命名空间派发),持久化走官方 ctx.settingsScope(保存即生效、revision 栅栏、key 的 presence 标记覆盖)。pluginCard() 把「scope 绑定 + 状态机 + 卡壳」一次性封装好:

const card = ui.pluginCard({
  key: 'my-plugin',                 // 必填:settings 命名空间 = Tab 派发键(白名单 ^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$)
  order: 10,                        // 可选:聚合层排序(keyed 槽自身无 order)
  locale: 'settings.myPlugin',      // 可选:透传 slot 字典 ns
  header: { title: '我的插件', desc: '一句话说明', meta: 'v1.0' },  // kit 渲染的卡头
  fields: [                         // 推荐:kit Rows 声明式表单
    { key: 'enabled', type: 'switch', label: '启用' },
  ],
  // 或 content: (ctx) => <element>  自由内容出口(ctx = { ui, store, scope, key })
  showIn: 'official-tab',           // 'official-tab' | 'settings-page' | 'both'
  chrome: 'full',                   // 'full'(kit 卡壳)| 'minimal'(只内容)
  api: { get, update },             // 可选:settings-page 无 scope 时的 fenced 回退
})
// card = { key, store, scope, showIn };card.store 为 settingsScope 后端
//   store.setField('enabled', false)  直接保存(save-as-you-go)
//   store.unsetField('enabled')       清回 composition 层
  • 传输 = ctx.settingsScope.bind({ namespace: key }):保存即生效、revision 栅栏与冲突恢复由官方 scope 保证,kit 不重造(校准注:勿把 host 侧 settings.watch/applies:'restart' 范式套到 pluginCard/settingsScope)。
  • settingsScope 是可选服务(kit 用 ctx.get('settingsScope') 探测,不进 inject 硬依赖):headless/无官方设置面缺席时,official-tab 路径给出清晰诊断(说明缺了什么、后果是什么)并拒绝注册;settings-page 路径可回退 fenced api
  • self-protectionkey 白名单校验 + 重复 key 先自查告警拒绝(官方 keyed 覆盖语义的自我保护)。
  • 卡壳由 kit 渲染(卡头/内容区/状态条),内容走 fields Rows 或 content 自由出口——对未来「统一管理 + 用户自定义壳」的能力位,靠服务/子槽注入,不 import 官方组件源码(对齐三原则)。

5. 通用设置统计卡(自动)

侧栏入口局限:官方侧栏只有 sidebar.footer.action 一个可叠加槽(sidebar.workspaces / sidebar.settings 都是单例槽),所以插件想在侧栏加「独立图标席位 / 平行工作位 / 额外设置入口」当前做不到,只能往底部动作区追加(task-board 入口即走此槽)。设置页内容(settings.section)与浮层(shell.overlay)则完全可自由叠加。详见 GUIDE.zh.md §4b「官方槽位边界与已知局限」。

kit 会在「设置 → 通用设置」自动注册一张卡片:显示当前通过 ui.section()ui.overlay()ui.pluginCard()(v0.3.0 起并入)接入的配置界面总数,点击卡片展开可查看具体插件名(导航文案 + id,浮层带「浮层」前缀,pluginCard 显示其 key)与 kit 版本号。计数实时跟随注册/卸载(section()/overlay()/pluginCard() 注册时以 registrant: 'dsh-settings-ui' 标记 ledger 条目,统计卡按该标记过滤——ledger 只保留白名单字段,registrant 是其中之一)。卡片文案跟随官方 locale 服务(kit 注册 dsh-settings-ui 字典命名空间,zh/en;locale 缺席时退回内置中文),消费方无需做任何事。

6. 浮层面板(v0.2):ui.overlay + ui.Panel + ui.createPanelStore

插件在窗口最前层级(官方 shell.overlay 槽,frame 级、click-through 层)创建自己的悬浮页面:

function AssistantPanel(props) {
  const { ui, panel } = props
  return ui.h(ui.Panel, { title: '桌面助手', panel, onClose: () => {} },
    // body 内容(复用全部原子组件与 sui-* 类)
    ui.h(ui.Card, {}, '……'),
  )
}

const plugin = {
  inject: ['slots', 'settingsUi'],
  apply(ctx) {
    const ui = ctx.settingsUi
    // store 在 apply 里创建一次(与 settings store 同铁律)
    const panel = ui.createPanelStore({ persist: 'my-assistant.panel.v1', initiallyOpen: false })
    ui.overlay({
      id: 'my-assistant-overlay', order: 100,
      inject: () => ({ ui, panel }),
      render: AssistantPanel,
    })
  },
}
  • ui.overlay(config):注册 shell.overlay 浮层;config: { id, order?, label?, render, inject?, locale? },带 registrant 标记。
  • ui.Panel:浮层 chrome(.sui-overlay-panel)——标题栏拖拽移动右下角拖拽 resize、最小化(/)、关闭(×)、点击置顶(kit 全局 z 计数器,多浮层点谁谁在上)、窗口缩放自动 re-clamp。props:{ title, panel, onClose?, style?, children }
  • ui.createPanelStore({ persist?, initiallyOpen? }):状态 { open, minimized, pos, anchor, size, z }open/close/toggle/toggleMinimized/move/setAnchor/resize/setZpersist 传 localStorage key 时位置/锚点/最小化/尺寸跨刷新保留
  • ui.usePanel(store):React 快照 hook。
  • 浮层样式类(.sui-overlay-panel/-head/-body/-title.sui-pill-tabs.sui-overlay-item/-list.sui-mark 等)随共享样式自动注入;面板默认靠右上、右侧停靠自动补偿 --dsh-sidebar-width(该变量由 dsh-better-sidebar 发布为右侧面板宽度,拖动时逐帧更新、收起时被移除;变量缺失回退 0px,与官方 --dsw-* token 无关)。

快速示例(完整插件)

window.__ModuleLoader__.load({
  id: 'my-settings-plugin',
  factory: (require) => {
    const React = require('react')
    async function call(method, payload = {}) { /* 同源 fetch,返回 json.value */ }

    function MySection(props) {
      const { ui, store } = props   // store 来自 inject(在 apply 里创建一次,不能在渲染里建)
      const s = ui.useSettings(store)
      React.useEffect(() => { void store.refresh() }, [store])
      return ui.h(React.Fragment, null,
        ui.SectionHeader({ title: '我的设置', desc: '一句话说明' }),
        ui.Card({},
          ui.Rows({
            fields: [{ key: 'enabled', type: 'switch', label: '启用' }],
            values: s.doc ?? {},
            onChange: (k, v) => store.set({ doc: { ...(s.doc ?? {}), [k]: v } }),
          }),
        ),
        s.error ? ui.Banner({ kind: 'error' }, s.error) : null,
        s.saved ? ui.Banner({ kind: 'saved' }, '已保存并生效') : null,
        ui.h('div', { className: 'sui-actions' },
          ui.Button({ kind: 'primary', disabled: s.busy, onClick: () => store.commit(s.doc) }, '保存'),
        ),
      )
    }

    const plugin = {
      name: 'my-settings-plugin',
      inject: ['slots', 'settingsUi'],
      apply(ctx) {
        // 关键:store 在 apply 里创建一次,通过 inject 传进组件;
        // 不要在组件渲染函数里 createSettingsStore(每次渲染新建 store → useSettings 依赖变化 → 死循环卡死页面)。
        const store = ctx.settingsUi.createSettingsStore({ get: () => call('get'), update: (p) => call('update', p) })
        ctx.settingsUi.section({
          id: 'my-settings-plugin', order: 300, label: () => '我的设置',
          inject: () => ({ ui: ctx.settingsUi, store }),
          render: MySection,
        })
      },
    }
    return plugin
  },
})

Roadmap(未来规划)

  • 1.0.0 功能更新ui.describeForm(消费官方 settings.describe 的 schemastery schema → 自动渲染表单 + 用户覆盖标注 + redactSecrets 只写框 + revision 冲突处理),构建在已落地的 settingsScope 后端之上。
  • 工程补强.d.ts 与实现自动校验(公开发布后类型漂移风险升值)。
  • 能力边界(官方契约限制,kit 不做):侧栏平行席位(sidebar.workspaces / sidebar.settings 为单例槽);浅色主题。
  • 已知限制(文档化):ToastHost 多实例同显(每插件只挂一个 host 即规避)。
  • 维护承诺:上游 rc 漂移时按既定方法论重跑契约核对;反馈请走 GitHub 讨论区评论。

家族单轨说明(rc.7 对齐)

rc7 官方把「配置卡上车」让给其插件配置 Tab(keyed settings.plugin.item,官方教程 adding-a-settings-card 已不再提 settings.section)。kit 的定位随之升级为「官方范式之上的加速层」:

  • 新品配置界面一律走 ui.pluginCard()(官方 Tab)——统一 keyed 卡 + settingsScope 轨道,避免「设置页一半、插件 Tab 一半」的两轨碎片化。
  • 存量 section() 仍完全支持,无需迁移(rc6 环境/传统设置页/headless 回退照常可用)。
  • 对齐三原则:①契约层只用官方槽/服务(settings.plugin.item / settings.section / shell.overlay / settingsScope / locale),绝不平行另造 settings 表面或 allowlist;②视觉层 .sui-* 全部解析自官方 token(--dsw-alias-* / --ds-font-family-code / --dsh-sidebar-width),硬编码仅作 token 缺失兜底;③代码层不 import 官方卡 chrome(client bundle 纯度门禁值导入),对齐靠契约+token+观感,卡壳/表单/状态机/a11y kit 自建但 token 对齐。