修复 DirectProject 历史分页失败状态并清理漂移绑定
首页历史读取失败时保留显示更早按钮状态并补充回归用例 恢复 DirectCodexUser 生成绑定契约,移除多余 envelope 与无引用 UI 绑定 同步 ts-rs 生成物排障共享记忆
This commit is contained in:
@@ -5,8 +5,8 @@ mod validation;
|
|||||||
mod wire;
|
mod wire;
|
||||||
|
|
||||||
pub(crate) use model::{
|
pub(crate) use model::{
|
||||||
DirectCodexUserContentPart, DirectCodexUserItem, DirectCodexUserMessageEnvelope,
|
DirectCodexUserContentPart, DirectCodexUserItem, DirectCodexUserMessageItem,
|
||||||
DirectCodexUserMessageItem, DirectCodexUserRole, DirectCodexUserRuntimeRegionPart,
|
DirectCodexUserRole, DirectCodexUserRuntimeRegionPart,
|
||||||
};
|
};
|
||||||
pub(crate) use validation::validate_direct_codex_user_item;
|
pub(crate) use validation::validate_direct_codex_user_item;
|
||||||
pub(crate) use wire::{
|
pub(crate) use wire::{
|
||||||
|
|||||||
@@ -61,13 +61,6 @@ pub(crate) struct DirectCodexUserRuntimeRegionPart {
|
|||||||
pub(crate) resource_ids: Vec<String>,
|
pub(crate) resource_ids: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
|
||||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
||||||
#[ts(export, export_to = concat!(env!("CARGO_MANIFEST_DIR"), "/../src/features/project-workspace/generated/"))]
|
|
||||||
pub(crate) struct DirectCodexUserMessageEnvelope {
|
|
||||||
pub(crate) item: DirectCodexUserItem,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -11902,7 +11902,11 @@ export function App({
|
|||||||
setDirectThreadChat((state) =>
|
setDirectThreadChat((state) =>
|
||||||
mergeDirectHistoryItems(state, pages.items),
|
mergeDirectHistoryItems(state, pages.items),
|
||||||
);
|
);
|
||||||
setDirectHistoryHasMore(pages.hasMore);
|
// 首页或中途读取失败时保留旧值:失败页没有可靠的 hasMore,不能因为
|
||||||
|
// 一次瞬时 IO 抖动把「显示更早」按钮永久收掉。
|
||||||
|
if (!pages.error) {
|
||||||
|
setDirectHistoryHasMore(pages.hasMore);
|
||||||
|
}
|
||||||
directHistoryOldestItemIdRef.current =
|
directHistoryOldestItemIdRef.current =
|
||||||
pages.firstItemId ?? directHistoryOldestItemIdRef.current;
|
pages.firstItemId ?? directHistoryOldestItemIdRef.current;
|
||||||
if (pages.error) {
|
if (pages.error) {
|
||||||
|
|||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file is generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
import type { DirectCodexUserRuntimeRegionPart } from './DirectCodexUserRuntimeRegionPart';
|
import type { DirectCodexUserRuntimeRegionPart } from './DirectCodexUserRuntimeRegionPart';
|
||||||
|
|
||||||
export type DirectCodexUserContentPart =
|
export type DirectCodexUserContentPart =
|
||||||
|
|||||||
+3
-7
@@ -1,9 +1,5 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file is generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
import type { DirectCodexUserMessageItem } from './DirectCodexUserMessageItem';
|
import type { DirectCodexUserMessageItem } from './DirectCodexUserMessageItem';
|
||||||
|
|
||||||
/**
|
export type DirectCodexUserItem = DirectCodexUserMessageItem;
|
||||||
* DirectProject 本轮 user input 的唯一结构化入口。
|
|
||||||
*/
|
|
||||||
export type DirectCodexUserItem = {
|
|
||||||
type: 'message';
|
|
||||||
} & DirectCodexUserMessageItem;
|
|
||||||
|
|||||||
-4
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { DirectCodexUserItem } from './DirectCodexUserItem';
|
|
||||||
|
|
||||||
export type DirectCodexUserMessageEnvelope = { item: DirectCodexUserItem };
|
|
||||||
+4
-2
@@ -1,9 +1,11 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file is generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
import type { DirectCodexUserContentPart } from './DirectCodexUserContentPart';
|
import type { DirectCodexUserContentPart } from './DirectCodexUserContentPart';
|
||||||
import type { DirectCodexUserRole } from './DirectCodexUserRole';
|
import type { DirectCodexUserRole } from './DirectCodexUserRole';
|
||||||
|
|
||||||
export type DirectCodexUserMessageItem = {
|
export type DirectCodexUserMessageItem = {
|
||||||
|
type: 'message';
|
||||||
role: DirectCodexUserRole;
|
role: DirectCodexUserRole;
|
||||||
content: Array<DirectCodexUserContentPart>;
|
content: DirectCodexUserContentPart[];
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file is generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type DirectCodexUserRole = 'user';
|
export type DirectCodexUserRole = 'user';
|
||||||
|
|||||||
+9
-9
@@ -1,13 +1,13 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
// This file is generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
export type DirectCodexUserRuntimeRegionPart = {
|
export type DirectCodexUserRuntimeRegionPart = {
|
||||||
label: string;
|
label: string;
|
||||||
runId: string | null;
|
runId?: string;
|
||||||
versionId: string | null;
|
versionId?: string;
|
||||||
elementTag: string | null;
|
elementTag?: string;
|
||||||
elementRole: string | null;
|
elementRole?: string;
|
||||||
text: string | null;
|
text?: string;
|
||||||
width: number | null;
|
width?: number;
|
||||||
height: number | null;
|
height?: number;
|
||||||
resourceIds: Array<string>;
|
resourceIds: string[];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import type {
|
import type {
|
||||||
DirectCodexUserContentPart,
|
DirectCodexUserContentPart,
|
||||||
DirectCodexUserItem,
|
DirectCodexUserItem,
|
||||||
|
DirectCodexUserMessageItem,
|
||||||
} from './generated';
|
} from './generated';
|
||||||
|
|
||||||
export type ResourceReferenceSource =
|
export type ResourceReferenceSource =
|
||||||
@@ -99,17 +100,16 @@ export function chatReferenceToContentPart(
|
|||||||
if (reference.type === 'resource') {
|
if (reference.type === 'resource') {
|
||||||
return { type: 'agc_resource_reference', resourceId: reference.resourceId };
|
return { type: 'agc_resource_reference', resourceId: reference.resourceId };
|
||||||
}
|
}
|
||||||
// 生成绑定里可选字段是 `T | null`(Rust `Option<T>` 会显式序列化成 null,不是省略键)。
|
|
||||||
return {
|
return {
|
||||||
type: 'agc_runtime_region_reference',
|
type: 'agc_runtime_region_reference',
|
||||||
label: reference.label,
|
label: reference.label,
|
||||||
runId: reference.runId ?? null,
|
runId: reference.runId,
|
||||||
versionId: reference.versionId ?? null,
|
versionId: reference.versionId,
|
||||||
elementTag: reference.elementTag ?? null,
|
elementTag: reference.elementTag,
|
||||||
elementRole: reference.elementRole ?? null,
|
elementRole: reference.elementRole,
|
||||||
text: reference.text ?? null,
|
text: reference.text,
|
||||||
width: reference.width ?? null,
|
width: reference.width,
|
||||||
height: reference.height ?? null,
|
height: reference.height,
|
||||||
resourceIds: reference.resourceIds,
|
resourceIds: reference.resourceIds,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -135,7 +135,7 @@ export function chatComposerDraftToDirectCodexUserItem(
|
|||||||
role: 'user',
|
role: 'user',
|
||||||
content,
|
content,
|
||||||
id,
|
id,
|
||||||
} satisfies DirectCodexUserItem;
|
} satisfies DirectCodexUserMessageItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resourceDisplayName(asset: GameCreationAppAssetManifestEntry) {
|
export function resourceDisplayName(asset: GameCreationAppAssetManifestEntry) {
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { NodeComponent } from "./NodeComponent";
|
|
||||||
import type { NodeId } from "./NodeId";
|
|
||||||
import type { StageStatus } from "./StageStatus";
|
|
||||||
|
|
||||||
export type BindingChange = { node_id: NodeId, component: NodeComponent, component_status: StageStatus, };
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
||||||
import type { BindingChange } from "./BindingChange";
|
|
||||||
|
|
||||||
export type BindingDTO = { changes: Array<BindingChange>, };
|
|
||||||
@@ -231,4 +231,23 @@ describe('DirectProject 历史分页连拉', () => {
|
|||||||
expect(result.hasMore).toBe(true);
|
expect(result.hasMore).toBe(true);
|
||||||
expect(result.error).toBe(failure);
|
expect(result.error).toBe(failure);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('首页读取失败时保留首屏锚点并返回错误', async () => {
|
||||||
|
const failure = new Error('首页读取失败');
|
||||||
|
const readSlice = vi.fn(async (_beforeItemId: string | null) => {
|
||||||
|
throw failure;
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await readDirectHistoryPages({
|
||||||
|
existingEntries: [],
|
||||||
|
beforeItemId: null,
|
||||||
|
readSlice,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(readSlice).toHaveBeenCalledTimes(1);
|
||||||
|
expect(result.items).toEqual([]);
|
||||||
|
expect(result.firstItemId).toBeNull();
|
||||||
|
expect(result.hasMore).toBe(false);
|
||||||
|
expect(result.error).toBe(failure);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5691,8 +5691,8 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
|||||||
|
|
||||||
- **现象**:在 `apps/ai-game-creator-shell/src-tauri` 跑过 `cargo test` 之后,`npm run ai-game-creator-shell:typecheck` 报 10 条类型错(`src/features/project-workspace/resourceReferences.ts:106-112` 的 `string | undefined` 不能赋给 `string | null`;同文件 134 行的对象字面量带 `type: 'message'`,而 `DirectCodexUserMessageItem` 里没有该字段),`npm run ai-game-creator-shell:build` 也死在 `beforeBuildCommand` 的同一条 typecheck 上。
|
- **现象**:在 `apps/ai-game-creator-shell/src-tauri` 跑过 `cargo test` 之后,`npm run ai-game-creator-shell:typecheck` 报 10 条类型错(`src/features/project-workspace/resourceReferences.ts:106-112` 的 `string | undefined` 不能赋给 `string | null`;同文件 134 行的对象字面量带 `type: 'message'`,而 `DirectCodexUserMessageItem` 里没有该字段),`npm run ai-game-creator-shell:build` 也死在 `beforeBuildCommand` 的同一条 typecheck 上。
|
||||||
- **原因**:crate 里的 ts-rs 导出会按**本机依赖版本**重写 `src/features/project-workspace/generated/DirectCodexUser*.ts`:注释头变成 "This file was generated…"、字符串改双引号、`DirectCodexUserMessageItem` 丢掉 `type: 'message'` 判别字段、并多出一个 `DirectCodexUserMessageEnvelope.ts`。仓库里提交的那份是前端真正依赖的形状(前端按带 `type` 的判别联合写),重写后两边就对不上——错在生成器版本漂移,不在前端。
|
- **原因**:crate 里的 ts-rs 导出会按**本机依赖版本**重写 `src/features/project-workspace/generated/DirectCodexUser*.ts`:注释头变成 "This file was generated…"、字符串改双引号、`DirectCodexUserMessageItem` 丢掉 `type: 'message'` 判别字段、并多出一个 `DirectCodexUserMessageEnvelope.ts`。仓库里提交的那份是前端真正依赖的形状(前端按带 `type` 的判别联合写),重写后两边就对不上——错在生成器版本漂移,不在前端。
|
||||||
- **处理(现行口径)**:不要把重写结果当改动提交。跑过 `cargo test` 或构建后先 `git checkout -- apps/ai-game-creator-shell/src/features/project-workspace/generated`,再删掉多出来的 `DirectCodexUserMessageEnvelope.ts`,然后才做 typecheck / 打包;绑定与前端形状冲突时以**已提交的绑定 + 前端**为基准排查。
|
- **处理(现行口径)**:不要把重写结果当改动提交。跑过 `cargo test` 或构建后只恢复 `apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUser*.ts` 的仓库版本,再删掉多出来的 `DirectCodexUserMessageEnvelope.ts`;不要恢复整个 `generated/` 目录,以免误删 DirectThread 的现役绑定。绑定与前端形状冲突时以**已提交的 DirectCodexUser 绑定 + 前端**为基准排查。
|
||||||
- **验证**:恢复提交版本后 `npm run ai-game-creator-shell:typecheck` exit 0(`[skill-pack] OK`);保留重写结果时同一条命令 exit 2。release 构建本身还会在 `src/features/ui-editor/types/` 落下 `BindingChange.ts` / `BindingDTO.ts` 两个无人引用的未跟踪文件,属同类生成产物。
|
- **验证**:恢复仓库版本后 `npm run ai-game-creator-shell:typecheck` exit 0(`[skill-pack] OK`);保留重写结果时同一条命令 exit 2。release 构建本身还会在 `src/features/ui-editor/types/` 落下 `BindingChange.ts` / `BindingDTO.ts` 两个无人引用的生成产物;它们不属于前端契约,发现后直接删除,不提交。
|
||||||
- **关联**:`apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/`(ts-rs 导出源)、`apps/ai-game-creator-shell/src/features/project-workspace/resourceReferences.ts`、`apps/ai-game-creator-shell/scripts/build-release.mjs`(`beforeBuildCommand`)。
|
- **关联**:`apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/`(ts-rs 导出源)、`apps/ai-game-creator-shell/src/features/project-workspace/resourceReferences.ts`、`apps/ai-game-creator-shell/scripts/build-release.mjs`(`beforeBuildCommand`)。
|
||||||
|
|
||||||
## 2026-09-16 Node 26 下 vitest 的 jsdom 用例拿不到 window.localStorage
|
## 2026-09-16 Node 26 下 vitest 的 jsdom 用例拿不到 window.localStorage
|
||||||
|
|||||||
Reference in New Issue
Block a user