fix: 回应独立 CR 的 P2/P3 意见(0×P1)

- P2-1 onOpen 收到真正落地的 tab:url 新建场景从 landed 态按 id 反查
  (补丁后对象),回调不再拿到未打 url 的 tab
- P2-2 插件设置行显式取值源:FeatureSettingsRows 新增 valueSource prop,
  插件行读自己的 pluginSettings blob——插件键与宿主 pref 同名时不再
  静默读宿主值(原投影合并有读宿主写插件的读写不对称);prop 命名避开
  Object.prototype.valueOf 冲突
- P2-3 consumer 类型检查接入工程:package.json 新增 check:consumer-types
  脚本 + CI 在 build 后执行;脚本补 lib/types 存在性前置检查(防假绿)
- P3-1 url seed 只在新建时应用:dedupe 聚焦的 tab 不再被覆写 path
- P3-2 closeTab/activateTab 未知 id 严格 no-op:store.reduce 对同引用
  结果短路(不 notify、不重写 localStorage),根因修复 + 回归测试
- P3-3 closeTab/activateTab 增加可选 scope(cwd 随回调传递),Sidebar
  双树激活/关闭路径传入 { sessionId, cwd }
- P3-4 定向打开不再自动展开不可见 session 的面板
- P3-5 Sidebar 终端 pty 释放查找覆盖双树(底部面板终端不再漏掉 HTTP
  兜底)
- P3-6/P3-7 与 Nits:AGENTS.md/指南文档化(dedupeKey 纯函数、meta 用
  null 清除、回调 scope 的 cwd 语义);consumer fixture 补常量导出覆盖

测试 +6(url 落地保真 / url 不覆写聚焦 / 未知 id 无 notify / 定向打开
不展开 / scope 随回调 / valueSource 键隔离),464 全绿;typecheck、
build、consumer 校验全部通过。
This commit is contained in:
Menghuan1918
2026-08-14 21:28:41 +08:00
parent 319d34ed09
commit 0995a58454
12 changed files with 263 additions and 67 deletions
+17 -12
View File
@@ -108,11 +108,6 @@ function viewerOrder(a: FileViewerDescriptor, b: FileViewerDescriptor): number {
return (b.priority ?? 0) - (a.priority ?? 0)
}
/** Read one boolean pref by declarative key (missing = false). */
function prefBool(prefs: SidebarPrefs, key: string): boolean {
return (prefs as unknown as Record<string, boolean>)[key] === true
}
/** Whether a feature declares any secondary settings (gear button shows). */
function hasSettings(feature: TabDescriptor | FileViewerDescriptor): boolean {
const settings = feature.settings
@@ -210,8 +205,15 @@ export function FeatureSettingsRows(props: {
* display (clamped for numbers, the current pref when the input is
* invalid). Optional: rows with no handler keep their draft. */
onCommit?: (toggle: SidebarSettingToggle, raw: string) => string
/** Explicit value source (v0.12.0+): when given, rows read their values
* from it instead of the `prefs` face — plugin-owned rows read their
* own blob, so a plugin key can never collide with (or silently read)
* a host pref of the same name. (Named `valueSource`, not `valueOf`:
* the latter collides with the inherited Object.prototype.valueOf.) */
valueSource?: (key: string) => unknown
}) {
const { toggles, prefs, onToggle, onCommit } = props
const { toggles, prefs, onToggle, onCommit, valueSource } = props
const read = valueSource ?? ((key: string): unknown => (prefs as unknown as Record<string, unknown>)[key])
return (
<div className={css.popupRows}>
{toggles.map(toggle => {
@@ -225,13 +227,13 @@ export function FeatureSettingsRows(props: {
</span>
<Switch
label={title}
checked={prefBool(prefs, toggle.key)}
checked={read(toggle.key) === true}
onChange={(next) => { onToggle(toggle, next) }}
/>
</div>
)
}
const value = String((prefs as unknown as Record<string, unknown>)[toggle.key] ?? '')
const value = String(read(toggle.key) ?? '')
// Keyed by the committed value: a failed commit reverts prefs, the
// key changes, and the row remounts with the stored value (typing
// never changes the key, so mid-edit drafts survive re-renders).
@@ -337,9 +339,11 @@ export function SettingsBody(props: {
const toggles = feature.settings?.toggles ?? []
const pluginToggles = feature.settings?.pluginToggles ?? []
if (toggles.length === 0 && pluginToggles.length === 0) return null
// Plugin rows read/write their values through a projected prefs face so
// the shared row renderer works unchanged (its rows read prefs[key]).
const effectivePrefs = { ...prefs, ...(prefs.pluginSettings[feature.id] ?? {}) } as SidebarPrefs
// Plugin rows read their values from the descriptor's OWN blob through
// an explicit value source — no projection onto the prefs face, so a
// plugin key can never collide with (or silently read) a host pref of
// the same name.
const pluginBlob = prefs.pluginSettings[feature.id] ?? {}
return (
<div className={css.popupRows}>
{toggles.length > 0 && (
@@ -353,9 +357,10 @@ export function SettingsBody(props: {
{pluginToggles.length > 0 && (
<FeatureSettingsRows
toggles={pluginToggles}
prefs={effectivePrefs}
prefs={prefs}
onToggle={onPluginToggle}
onCommit={onPluginCommit}
valueSource={(key) => pluginBlob[key]}
/>
)}
</div>