Uh oh!
There was an error while loading. Please reload this page.
fix(app-shell): honour all three persist values in ContextSelectors - #3922
Conversation
`AppContextSelectorSchema.persist` declares one persistence medium per value
("Persist selection via URL query, sessionStorage, or not at all"), but the
shell mirrored every selector into BOTH stores and read them back as
`URL ?? storage`. So `'session'` and `'query'` were indistinguishable, and
`'none'` only opted out of the storage-to-URL re-apply while still writing
both.
Each value now names exactly one medium, on the read side as well as the
write side:
- `'query'` (default) — the URL query key only; storage is neither written
nor read, so a stale `objectui-ctx-*` entry from an older build is ignored
rather than resurrected.
- `'session'` — sessionStorage only; the scope stays out of the address bar,
and a query parameter of the same key no longer shadows it.
- `'none'` — neither store; the pick lives in component state for that mount
and dies with it.
The storage-to-URL repair effect goes with the mirror it depended on: a
`'query'` scope dropped by a param-less nav link is re-established by
`SelectorControl`'s auto-select-first, which was that effect's stated reason
to exist. The dev-only "two selectors share one scope key" warning now
considers `'query'` selectors only, since no other value touches the URL.
Studio declares `persist: 'query'` and is the only live consumer, so its
package scope is what changes: the undeclared sessionStorage mirror is gone,
and a param-less navigation lands on the first project package instead of the
last picked one. The README's Studio package scope section is updated to say
so.
Refs objectstack-ai/objectstack#5994
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRtThe latest updates on your projects. Learn more about Vercel for GitHub. |
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
commented
Aug 9, 2026
✅ 验收通过(objectui 分片 PM,session 实物核验:base 技术裁定意见:
勘误:PM 认领评论把主落点误写为 GraphQL 配额未恢复则 undraft+auto-merge 由 PM 随后补发。 Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixesobjectstack-ai/objectstack#5994
按维护者裁定(enforce,不退役;spec 不改)兑现
AppContextSelectorSchema.persist的三个取值。spec 对该键的原话是 “Persist selection via URL query, sessionStorage, or not at all” —— 一值一介质。而packages/app-shell/src/layout/ContextSelectors.tsx此前把每个选择器同时写进 sessionStorage 和 URL,读取侧再按URL ?? storage兜一次:于是'session'与'query'完全无差别,'none'也只是跳过「从 storage 回灌 URL」那一个 effect,照样两处都写。行为矩阵(三值 × 写入/恢复)
'query'(默认)objectui-ctx-*条目既不读也不删,直接忽略'session'objectui-ctx-{app}-{id}'none'读取侧与写入侧同等排他:
'session'选择器不再从 query string 读回,'query'选择器也不再从 storage 读回 —— 那个??正是两值同化的根因。想同时要「URL 反映」和「记忆兜底」,应当去 spec 加一个新取值,而不是让渲染器背着作者写第二处存储。一并删掉的 storage → URL 回灌 effect
它是「两处都写」的配套设施:URL 缺参时用 storage 补回。一值一介质之后已无可桥接之物 ——
'query'作用域被无参导航丢掉时,由SelectorControl的 auto-select-first 重新确立(「不能停在空值上」本来就是该 effect 自述的存在理由);想要超出 URL 的记忆,作者显式声明'session',代价是不进地址栏。dev 环境下「两个选择器撞同一个 scope 键」的告警随之只看
'query'选择器:其余取值根本不碰 URL,再告警就是误报。与裁定注记不符的一处实况(请复核)
裁定写道「默认仍是
'query',只有显式声明'session'/'none'的作者会看到变化」。实测并非如此:Studio 在 framework 仓packages/platform-objects/src/apps/studio.app.ts显式声明了persist: 'query',且它是全仓唯一声明contextSelectors的应用(objectui 里没有任何元数据声明过persist)。所以本 PR 唯一影响到的就是它,而变化恰好是「只有你声明的那一处才生效」:?package=的读写一切照旧 —— 六个 Studio 读取面、以及约 15 条导航元数据里的params: { package: '{active_package}' },全不受影响;objectui-ctx-studio-active_package;第三条是本 PR 唯一的用户可见退化,它是「兑现声明」的直接后果而非附带损伤:作者声明的介质是 URL,URL 里没有,就没有。
packages/app-shell/README.md的 Studio package scope 段落原写着 “repairs missing?package=… from the last selected package”,已按此改正。测试
ContextSelectors.persist.test.tsx:三值 × (写 URL / 写 storage / 恢复来源) 全矩阵,外加「persist省略时套用 spec 的'query'默认」「'none'在 remount 后丢弃」「非'query'选择器不再触发撞键告警」。每个用例都断言两个存储而非仅该取值命名的那一个 —— 「写了 URL」只是半句话,「且没碰 storage」才是坏掉的那半句。origin/main,19 个用例 12 转红、7 保持绿;保持绿的那 7 个断言的事实本来就成立(「从 URL 读回」、scope 键派生、legacy?package=兼容钉、两个'query'选择器撞键告警、模板变量发布)。三个'none'用例也在转红之列 —— 测试文件里记了原因:'none'的内存值来自新增 state,单独恢复被删的那两条写入并不会让它红,只有整体退回才会。ContextSelectors.scopeKey.test.tsx逐条重判(不是批量重写):一条断言翻转(默认取值下 storage 必须为空);一条整体替换 —— 原 “re-applies each remembered scope under its own key” 钉的正是被删的回灌桥,而且它本就无法诚实报告:active_env存的'prod'恰好也是它 auto-select 的首个选项,那一半即使什么都没恢复也会绿。替换后的用例改在persist: 'session'上钉「两个选择器、两把 storage 键、互不干扰、且都不进 URL」,两个取值都刻意避开首选项。整目录首跑曾有 1 个
waitFor超时的偶发红(随后连跑 3 次全绿,且本 PR 两个文件单跑 5/5 绿),报告为偶发而非隐瞒:失败栈是@testing-library的 real-timer 超时,即 AGENTS.md 记的「满并行下有界窗口被无界模块加载吃掉」那一类。本 PR 的 fixture 未声明icon,getIcon(undefined)返回静态导入的兜底图标,断言路径上没有 lazy 边界。影响面核对
useAppContextSelectors的消费者只有AppSidebar/UnifiedSidebar,两者仅取contextValues与element;全仓objectui-ctx的引用除本文件外只在上述两个测试文件里。其余 layout 测试的 fixture 均未声明contextSelectors,已逐个 grep 确认。Generated by Claude Code