Replies: 1 comment
|
这一族现在有八个独立线程了(我在 #1144 那边把七帖 / 十三个以上具名插件 / 三份独立 patch 脚本并成了一张表,就不在这里重复)。我来这条只为一件事:你的第 3 条被自己的提案埋掉了,而它可能是整族里最该先做的一件。 你的建议 3 是唯一一条不需要 RFC 就能做的
其余各帖(包括你的 1、2)要的都是注册期暴露声明——那需要改 seam 契约、复核全部现有注册点、还要先有 fail-closed 的脱敏路径(官方设计笔记把这三条列为暂缓理由)。而你的第 3 条一条都不沾:它不改 seam、不动注册点、不放宽任何暴露边界,只是别把已经产生的错误丢掉。 而它的收益是整族级别的。你自己那段排查过程就是证据:
这一族十三个以上的插件作者,每一个都独立走过这条路:点了没反应 → 怀疑自己的 schema → 怀疑自己的 client 代码 → 怀疑 DSH 坏了 → 最后才找到 apiproxy 里那个常量。后端每一次都清清楚楚地说了 如果那句错误当初就显示出来,这八个线程里的绝大多数根本不会以"排查记录"的形式存在——它们会直接以"请开放白名单"的形式出现,作者们省下的是各自几个小时的逆向。 所以我建议你把第 3 条从提案的第 3 项提到最前面,或者干脆单独开一帖,标题直写后果,比如 "Settings write failures are swallowed by the client: 这属于一个反复出现的类"后端已经把话说清楚了,前端把它扔了"——这个形状我最近在这个社区数到第七次:
共同点是同一句话:诊断信息在系统里真实存在过,只是没送到需要它的人手上。 而每一次的代价都是一次本可避免的逆向工程。 (#902 那位的经历最能说明问题:后端返回的消息里 preset 名、loader entry、包名、冲突对象、文件路径全都有——带上这条报错是十分钟的事,不带是一次逆向工程。) 如果要把你的第 3 条提成独立条目,我建议引上这张表——一个"错误被吞掉"的孤例容易被当成 UI 细节,七个实例是一条工程规范:任何 边界与利益相关我们不修 DSH 自家组件—— 利益相关:我维护 pi2dsh(Pi 生态兼容层),我们也被这条白名单挡住过(详见 #1144 那边),所以我是这一族的受益者之一。这条不推销:你要的三条都是 DSH 自己的活。 |
Uh oh!
There was an error while loading. Please reload this page.
摘要 Summary
English
settings.register()has no "expose" option, and the exposure allowlist ishardcoded in
dsh-host-apiproxy(WEB_SETTINGS_NAMESPACES/PRODUCT_SETTINGS_NAMESPACES). Third-party plugins therefore cannot persistconfiguration through the official
settingsScopepath — writes returnsettings-not-exposedand the client swallows the failure silently, so thesettings UI appears "unresponsive". The source comments even mark this as
deferred work, so this is a known gap worth scheduling. This post describes
the problem with a concrete reproduction, its impact on the plugin
ecosystem, and a backward-compatible fix proposal.
中文
settings.register()没有 "expose"(暴露)选项,可暴露 namespace 的白名单被硬编码在dsh-host-apiproxy中(WEB_SETTINGS_NAMESPACES/PRODUCT_SETTINGS_NAMESPACES)。因此第三方插件无法通过官方的
settingsScope路径持久化配置——写操作返回settings-not-exposed,且客户端静默吞掉这个错误,设置界面表现为"点了没反应"。源码注释甚至已将其标记为 deferred work,所以这是一个已知的、值得排期的缺口。
本帖给出问题的具体复现、对插件生态的影响,以及一个完全向后兼容的修复方案。
现状
packages/settings/settings/src/index.ts中的settings.register(ns, schema, options)注册的 namespace,默认对浏览器配置客户端既不可读也不可写。
packages/host/apiproxy/src/api-proxy.ts中:WEB_SETTINGS_NAMESPACES(第 126 行起)PRODUCT_SETTINGS_NAMESPACES(第 256 行)exposedNamespaces()(第 1953 行)只放行动态注册的 model providernamespace 以及上述两个硬编码集合。
settingsWrite(第 2009 行)被以settings-not-exposed拒绝;settings.describe也会把这些 namespace 完全过滤掉。settings.register(), so a plugin can expose its own configuration withouta change in this package, is deferred work." —— 官方已知,延后中。
影响
(
settingsNamespace+settings.register+ 客户端settingsScope)持久化配置,都会静默失败。
ui-theme在白名单内,能持久化到~/.dsh/settings.yaml;第三方主题包只能退而求其次用localStorage——同一领域存在两种持久化范式。
SettingsScopeController.write()(
packages/client/ui-settings/src/client/settings-scope.ts)捕获settings-not-exposed后只重读、不暴露任何错误,设置界面看起来"死了",开发者无从下手。
dsh-theme-pack)的"在侧边栏显示主题快捷入口"复选框点击无任何反馈;排查确认是
settings.mutate返回settings-not-exposed被客户端静默吞掉。"默认主题"按钮同样受影响(只有基于 localStorage 的"当前主题"正常)。
建议方案
核心思路:把"暴露"从 apiproxy 的硬编码白名单,提升为
settings.register()上的显式声明。packages/settings/settings/src/index.ts—— 给register()的 options增加
expose?: boolean(默认false,完全向后兼容),并新增exposedRegistrations(): string[]查询。packages/host/apiproxy/src/api-proxy.ts—— 让exposedNamespaces()合并
ctx.get('settings')?.exposedRegistrations()。一处改动同时覆盖describe过滤与settingsWrite拦截。packages/client/ui-settings/src/client/settings-scope.ts—— 不再吞掉写失败:把
settings-not-exposed/settings-rejected暴露给订阅者(console.warn 或在 snapshot 上增加
error字段)。describe的writable从 provider 级细化到 per-namespace,让设置 UI 能正确禁用只读 namespace 的表单。
验收条件
settings.register(ns, schema, { expose: true })后,浏览器设置页能读写该 namespace,并持久化到
~/.dsh/settings.yaml。expose的注册行为与现在完全一致(默认不暴露)。English Key Points
settings.register()cannot mark a namespace as exposed to thesettings UI; the allowlist is hardcoded in
dsh-host-apiproxy, sothird-party plugins get
settings-not-exposedand the client swallows it —settings controls appear dead. (Marked as deferred work in the source.)
vs third-party plugins treated unequally; silent failures are hard to debug.
expose?: booleantosettings.register()options; mergeexposedRegistrations()intoexposedNamespaces(); surface write failures on the client instead ofswallowing them.
on click;
settings.mutatereturnssettings-not-exposed.All reactions