09ed7b099b
- 原缺陷:预览 Hook 的预取作用域 key 缺省退化为"被喂进来的资源集合签名",而宿主按设计要求恒传**全量投影**(传全量才不会让总览里其它栏目只剩栏目标题栏),换栏目/换视图一字不改 ⇒ key 不变 ⇒ 取消上一视图排队预取的 effect 永不执行,"同级裁决"一并退化为 no-op。已有用例用"改 resources"模拟切视图,测的是生产不存在的调用方形状。
- resourceCanvasController.ts 新增 createResourceCanvasPrefetchScopeKey({ view, target, mode }):由视图身份(main / child:<栏目>)加排序模式算出标识。
- index.tsx 在资源画布处显式算出该标识并传给预览 Hook(栏目取 resourceBookState.view / resourceBookState.category,排序取 sortMode)。
- useProjectResourceCardPreviews.ts 把 prefetchScopeKey 由可选改为**必填**并删掉那段兜底:它能"看起来还在工作",正是它掩盖了漏接线;现在漏传是类型错误而不是静默少取消一次。
- 断言:resourceCanvasController.test.ts 新增标识派生两组用例(换栏目/换视图/换排序模式必须变,同一视图必须稳定);useProjectResourceCardPreviews.test.ts 改写两条既有切视图用例为生产形状(resources 恒为全量、只改视图标识与 eagerResources),并在用例内前置断言"换栏目必须换标识";其余既有用例经用例侧包装补齐标识,形状与生产一致,断言一条未放宽。
- 变异验证(提交前已跑):把 createResourceCanvasPrefetchScopeKey 的栏目分量去掉(只留 view + mode)→ 3 条用例变红(标识派生 / 取消上一视图排队预取 / 取消后重新可见再入队),还原后复跑 34 passed。
- 门禁:`npx vitest run` 上述两文件 34 passed;typecheck 见随后整轮。
28 lines
1.2 KiB
TypeScript
28 lines
1.2 KiB
TypeScript
export function createResourceCanvasPageCategorySignature<
|
||
TCategory extends string,
|
||
>(resources: readonly { category: TCategory }[]) {
|
||
return resources
|
||
.map((resource) => resource.category)
|
||
.sort()
|
||
.join('|');
|
||
}
|
||
|
||
/**
|
||
* 「当前为谁预取」的视图标识(`useProjectResourceCardPreviews` 的 `prefetchScopeKey`)。
|
||
*
|
||
* 它必须由**视图身份**算出,不能拿喂给预览 Hook 的资源集合签名代替:那个集合是
|
||
* **全量投影**(`index.tsx` 的注释写明收窄它会让总览里其它栏目只剩栏目标题栏),
|
||
* 换栏目/换视图**一字不改**,于是"资源 id 签名"恒不变 —— 视图切换时取消上一视图
|
||
* 排队预取的逻辑就成了一条永不执行的死代码,"同级裁决"也一并退化为 no-op。
|
||
*
|
||
* 组成:视图(`main` 总览 / `child:<栏目>`)+ 排序模式。同一栏目换排序会整体重排卡片,
|
||
* 对用户就是换了一次视图,因此也纳入标识。
|
||
*/
|
||
export function createResourceCanvasPrefetchScopeKey(input: {
|
||
view: 'main' | 'child';
|
||
target: string | null;
|
||
mode: string;
|
||
}) {
|
||
return `${input.view}:${input.target ?? ''}|${input.mode}`;
|
||
}
|