Skip to content

contextSelectors 的 URL 作用域键硬编码为 package:声明第二个 selector 会镜像第一个;persist 的三个取值未被区分 #3500

Description

@hotlong

实现 objectstack#5891(PR objectstack#5984,给 content/docs/ui/apps.mdxcontextSelectors 散文)时核对真实消费方发现的越界项,未在该 PR 内修;那份文档已就此加了一条「One scope per app today」的警示 callout,以免作者照着数组类型声明出一份静默失效的元数据 —— 这里是把实现侧的账单独记下来。

现象

packages/app-shell/src/layout/ContextSelectors.tsxuseAppContextSelectors 把 URL 上的作用域键硬编码成字面量 'package',而不是从 selector 的 id 推导。三处:

  • 重新施加已记住的作用域:if (p.get('package')) continue;p.set('package', saved);
  • 写入选中值:if (value) next.set('package', value); else next.delete('package');
  • 读回当前值:contextValues[sel.id] = (params.get('package') ?? saved) || (sel.allValue ?? '');

sessionStorage 那一半倒是按 selector 分开的(objectui-ctx-${appName}-${sel.id}),但只要 URL 上有 ?package=,?? 左边命中,所有 selector 的 contextValues 都取到同一个值。

后果

  1. 第二个 selector 不成立。 spec 的 App.contextSelectors 是数组(AppContextSelectorSchema[]),.describe 明说「selected value is exposed as the nav template var { id }」—— 按 id 命名。今天声明 active_package + active_env 两个,切任一个都写同一个 query key,两个模板变量拿到同一个值。作者得到的是一份 parse 通过、渲染出两个下拉、语义静默错乱的元数据。目前只有 Studio 一个内置 app 用了单个 selector,所以线上没人踩到 —— 但这正是 ADR-0049「声明了就得兑现」要防的形状。
  2. persist 的三个取值没有被区分。 schema 声明 'query' | 'session' | 'none'(默认 'query'),实现里 'none' 只是跳过了「从 sessionStorage 重新施加」那个 effect;setValue 无论如何都同时写 sessionStorage query 参数,所以 'none' 并没有「不持久化」,'session''query' 也没有区别。同样是 declared-but-unenforced 一类。

建议范围

  • 作用域键改为按 selector 派生(如 sel.id,或显式声明的 query 键),读写两侧一致;Studio 现有的 ?package= 需要一条兼容路径 —— active_packagepackage 不同名,这一步是真的迁移而不是纯改名(UnifiedSidebar.tsx:345 也直接读 ?package,一并算进来)。
  • persist 要么按声明兑现三个取值,要么按 ADR-0049 走 enforce-or-remove 把它收掉(spec 侧的事,需跨仓协调)。
  • 两条都值得配 selector 数量大于 1 的单测 —— 今天没有任何用例声明第二个 selector,所以这个洞在测试里也是不可见的。

真值来源:spec packages/spec/src/ui/app.zod.tsAppContextSelectorSchema;消费方 ContextSelectors.tsx / AppSidebar.tsx / UnifiedSidebar.tsx / packages/layout/src/NavigationRenderer.tsx(applyNavTemplate 按变量名查 contextValues,所以它那一侧本来就是按 id 分开的)。

参考:objectstack#5891 / PR objectstack#5984(本发现的来源)。


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions