修复 DirectProject 历史分页失败状态并清理漂移绑定

首页历史读取失败时保留显示更早按钮状态并补充回归用例

恢复 DirectCodexUser 生成绑定契约,移除多余 envelope 与无引用 UI 绑定

同步 ts-rs 生成物排障共享记忆
This commit is contained in:
2026-09-18 01:42:29 +08:00
parent 254f3fcc5d
commit 467b712da7
14 changed files with 56 additions and 55 deletions
@@ -5,8 +5,8 @@ mod validation;
mod wire;
pub(crate) use model::{
DirectCodexUserContentPart, DirectCodexUserItem, DirectCodexUserMessageEnvelope,
DirectCodexUserMessageItem, DirectCodexUserRole, DirectCodexUserRuntimeRegionPart,
DirectCodexUserContentPart, DirectCodexUserItem, DirectCodexUserMessageItem,
DirectCodexUserRole, DirectCodexUserRuntimeRegionPart,
};
pub(crate) use validation::validate_direct_codex_user_item;
pub(crate) use wire::{
@@ -61,13 +61,6 @@ pub(crate) struct DirectCodexUserRuntimeRegionPart {
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)]
mod tests {
use super::*;
+5 -1
View File
@@ -11902,7 +11902,11 @@ export function App({
setDirectThreadChat((state) =>
mergeDirectHistoryItems(state, pages.items),
);
setDirectHistoryHasMore(pages.hasMore);
// 首页或中途读取失败时保留旧值:失败页没有可靠的 hasMore,不能因为
// 一次瞬时 IO 抖动把「显示更早」按钮永久收掉。
if (!pages.error) {
setDirectHistoryHasMore(pages.hasMore);
}
directHistoryOldestItemIdRef.current =
pages.firstItemId ?? directHistoryOldestItemIdRef.current;
if (pages.error) {
@@ -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';
export type DirectCodexUserContentPart =
@@ -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';
/**
* DirectProject 本轮 user input 的唯一结构化入口。
*/
export type DirectCodexUserItem = {
type: 'message';
} & DirectCodexUserMessageItem;
export type DirectCodexUserItem = DirectCodexUserMessageItem;
@@ -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 };
@@ -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 { DirectCodexUserRole } from './DirectCodexUserRole';
export type DirectCodexUserMessageItem = {
type: 'message';
role: DirectCodexUserRole;
content: Array<DirectCodexUserContentPart>;
content: DirectCodexUserContentPart[];
id: string;
};
@@ -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';
@@ -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 = {
label: string;
runId: string | null;
versionId: string | null;
elementTag: string | null;
elementRole: string | null;
text: string | null;
width: number | null;
height: number | null;
resourceIds: Array<string>;
runId?: string;
versionId?: string;
elementTag?: string;
elementRole?: string;
text?: string;
width?: number;
height?: number;
resourceIds: string[];
};
@@ -13,6 +13,7 @@ import {
import type {
DirectCodexUserContentPart,
DirectCodexUserItem,
DirectCodexUserMessageItem,
} from './generated';
export type ResourceReferenceSource =
@@ -99,17 +100,16 @@ export function chatReferenceToContentPart(
if (reference.type === 'resource') {
return { type: 'agc_resource_reference', resourceId: reference.resourceId };
}
// 生成绑定里可选字段是 `T | null`Rust `Option<T>` 会显式序列化成 null,不是省略键)。
return {
type: 'agc_runtime_region_reference',
label: reference.label,
runId: reference.runId ?? null,
versionId: reference.versionId ?? null,
elementTag: reference.elementTag ?? null,
elementRole: reference.elementRole ?? null,
text: reference.text ?? null,
width: reference.width ?? null,
height: reference.height ?? null,
runId: reference.runId,
versionId: reference.versionId,
elementTag: reference.elementTag,
elementRole: reference.elementRole,
text: reference.text,
width: reference.width,
height: reference.height,
resourceIds: reference.resourceIds,
};
}
@@ -135,7 +135,7 @@ export function chatComposerDraftToDirectCodexUserItem(
role: 'user',
content,
id,
} satisfies DirectCodexUserItem;
} satisfies DirectCodexUserMessageItem;
}
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.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 上。
- **原因**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 / 打包;绑定与前端形状冲突时以**已提交的绑定 + 前端**为基准排查。
- **验证**:恢复提交版本后 `npm run ai-game-creator-shell:typecheck` exit 0`[skill-pack] OK`);保留重写结果时同一条命令 exit 2。release 构建本身还会在 `src/features/ui-editor/types/` 落下 `BindingChange.ts` / `BindingDTO.ts` 两个无人引用的未跟踪文件,属同类生成产物。
- **处理(现行口径)**:不要把重写结果当改动提交。跑过 `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` 两个无人引用的生成产物;它们不属于前端契约,发现后直接删除,不提交
- **关联**`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