越界发现,记录于 #3537 (给 runner 补 api 查询参数文档)期间。未在该 PR 中顺手修改 —— 那是纯文档单,这里要动的是 packages/runner/src/App.tsx 的实现。PR #3537 已把这一行为如实写成注意事项,但它本身更像缺陷而不是设计,故另立单。
事实(origin/main,file:line)
加载器只在挂载时决定一次 —— packages/runner/src/App.tsx:44-57,useMemo(..., []),依赖数组为空:
const loader = useMemo ( ( ) => {
const params = new URLSearchParams ( window . location . search ) ;
const apiUrl = params . get ( 'api' ) ;
if ( apiUrl ) return new NetworkLoader ( apiUrl ) ;
return new LocalBundleLoader ( ) ;
} , [ ] ) ;
站内跳转只 push 路径,不带 query —— App.tsx:73-77:
const handleNavigate = useCallback ( ( to : string ) => {
window . history . pushState ( { } , '' , to ) ;
...
to 来自侧栏导航项的 item.path(packages/runner/src/LayoutRenderer.tsx:104-105 的 a href={item.path} → LayoutRenderer.tsx:151-157 的 handleNavClick → onNavigate(path)),是裸路径。
后果
用 ?api=https://backend.example.com/api 打开 runner → 正常走 NetworkLoader;点一次侧栏导航后,地址栏变成 /customers(?api= 没了)。当前会话不受影响(loader 实例已 memo 住),但:
刷新(F5) → params.get('api') 为 null → 退回 LocalBundleLoader;而 src/app-data/ 在正常安装里是空的(gitignore),于是所有加载返回 null,页面变成 Page not found / 兜底欢迎页。
复制地址栏分享给同事 → 对方拿到的是不带后端的 URL,复现不了你看到的东西。
失败现场不指认根因:没有任何提示说「你刚才那个 API 基址丢了」,控制台只会打印 📦 Using Local Bundle Loader。
可选方向(不预设结论)
handleNavigate 保留当前 query string(pushState({}, '', to + window.location.search))—— 最小改动,?api= 全程可见、可分享、刷新可复现。
把 API 基址从 query 提到 sessionStorage/localStorage,query 只作为一次性入口 —— 但会引入「地址栏与实际后端不一致」的新歧义,且与 ADR-0054 C3「可寻址状态放 URL」的口径相反。
判定为设计如此(query 只是本地调试开关),那就只留 docs(runner): runner 真正的 API 基址配置面是 api 查询参数,但全仓文档零处记载 #3537 的文档注意事项,本单关掉。
按 ADR-0054 C3 的分类,「当前指向哪个后端」是典型的可寻址状态 (用户会分享、刷新后期待还在),方向 1 与之最一致;但这是维护者判断,故不预设。
Generated by Claude Code
越界发现,记录于 #3537(给 runner 补
api查询参数文档)期间。未在该 PR 中顺手修改 —— 那是纯文档单,这里要动的是packages/runner/src/App.tsx的实现。PR #3537 已把这一行为如实写成注意事项,但它本身更像缺陷而不是设计,故另立单。事实(
origin/main,file:line)加载器只在挂载时决定一次 ——
packages/runner/src/App.tsx:44-57,useMemo(..., []),依赖数组为空:站内跳转只 push 路径,不带 query ——
App.tsx:73-77:to来自侧栏导航项的item.path(packages/runner/src/LayoutRenderer.tsx:104-105的a href={item.path}→LayoutRenderer.tsx:151-157的handleNavClick→onNavigate(path)),是裸路径。后果
用
?api=https://backend.example.com/api打开 runner → 正常走 NetworkLoader;点一次侧栏导航后,地址栏变成/customers(?api=没了)。当前会话不受影响(loader 实例已 memo 住),但:params.get('api')为 null → 退回LocalBundleLoader;而src/app-data/在正常安装里是空的(gitignore),于是所有加载返回null,页面变成Page not found/ 兜底欢迎页。失败现场不指认根因:没有任何提示说「你刚才那个 API 基址丢了」,控制台只会打印
📦 Using Local Bundle Loader。可选方向(不预设结论)
handleNavigate保留当前 query string(pushState({}, '', to + window.location.search))—— 最小改动,?api=全程可见、可分享、刷新可复现。sessionStorage/localStorage,query 只作为一次性入口 —— 但会引入「地址栏与实际后端不一致」的新歧义,且与 ADR-0054 C3「可寻址状态放 URL」的口径相反。api查询参数,但全仓文档零处记载 #3537 的文档注意事项,本单关掉。按 ADR-0054 C3 的分类,「当前指向哪个后端」是典型的可寻址状态(用户会分享、刷新后期待还在),方向 1 与之最一致;但这是维护者判断,故不预设。
Generated by Claude Code