feat(frontend): add MS2 skeleton with module contracts - #59
Conversation
The repository has no frontend workspace yet, so Issue 1024XEngineer#58 needs a buildable base before any module code can land. Add the Vite + React + TypeScript project with Tailwind, oxlint and Vitest configuration, the @/ path alias, and a dev proxy forwarding /api to the local backend on port 8000. The workspace now builds, type-checks and runs tests, giving later commits somewhere to put shared, entities and page code.
Business code needs one way to reach the backend that works both before and after the API exists, without touching call sites when switching. Add shared/api with real fetch client, mock handlers and response mappers behind a single request surface, plus the async-state hook, PageHeader component and test helpers. Entities can now read and write data through one entry point, and the mock and real implementations stay interchangeable via VITE_USE_MOCK.
Every business module depends on the data layer, so its exports have to be fixed before page or feature code can be written against them. Add the entities module with project, character, workflow-run, action-template and wearable slices behind a single index, including workflow selectors and local persistence while the backend does not store workflows yet. The backend can now read one file to see which endpoints and fields are expected, and callers keep the same signatures once the API takes over.
Issue 1024XEngineer#58 requires the module boundaries named in MODULES.md to exist as real code with declared interfaces, not just as directories. Add the workflow editor and inspection preview page modules with explicit props and no Router dependency, four feature slices, the routing shell, a global error boundary and a not-found route. Quick start now creates a run and the editor loads it by the same runId, and each page module can be rendered and tested outside the router.
Layer rules and module entries are only real if something fails when they are broken; manual grep review misses export-from and dynamic imports. Add unit tests for workflow selectors, mock contract and the error boundary, plus integration tests that parse every import with the TypeScript AST to enforce layering, slice isolation, module entries and cycle freedom. Twenty-nine tests now guard the architecture, and violations such as deep cross-slice imports or page-to-page references fail the suite.
The 07-24 review noted that a layered directory tree does not by itself show which modules were chosen or why, and readers cannot tell settled contracts from proposals. Add MODULES.md declaring four module boundaries with their entries, responsibilities, explicit non-responsibilities and unfrozen items, and a README covering setup, layering and the state of every backend contract. Reviewers can now see the module list, what each one refuses to own, and which interfaces still need a joint frontend-backend walkthrough.
|
|
||
| render(): ReactNode { | ||
| const { error } = this.state | ||
| if (!error) return this.props.children |
There was a problem hiding this comment.
[P2] 路由切换无法清除错误态。 一旦任意页面触发该边界,error 会一直保留;顶部导航虽然仍可点击,但 <Routes> 换成新页面后这里仍只返回 fallback,与上方“用户能自己走开”的契约相反。请在 location 变化时重置边界(例如由路由适配层按 pathname/key 重新挂载),并补一个抛错后导航到正常页面的测试。
The submit call ignored its command argument, so add-action never appended anything and the two entry points could not share one advance path. Take a command instead of a step id, guard it against availableCommands, append paired action steps, and derive status from remaining work rather than hardcoding running. AI suggestions and manual clicks now advance the same run through one interface, and a run with no pending step is no longer reported as running.
Quick Start navigated to the workflow editor after creating a run, turning two parallel entry points into a chain and defeating the purpose of the AI shortcut. Drive the run in place with suggestNextCommand and submitWorkflowStep under a step ceiling, subscribe by runId instead of holding a snapshot, and surface the run even when advancing fails. The AI entry now completes a whole workflow without leaving the page, and both entries are proven to share one command interface.
Nothing failed when Quick Start navigated away, because no test rendered the router or checked the resulting location. Render the whole App, run the AI flow to completion and assert the pathname stays at the root with no workflow editor heading present. A future reintroduction of the navigation call now breaks the suite instead of passing silently.
Both documents still described Quick Start as the way into the workflow editor, and the module contract implied a single entry chain. Rewrite the entry description so the AI page and the manual editor are parallel, and note that reject-frame does not yet create rework steps. Readers and reviewers now see two independent entries sharing one workflow boundary, with the remaining gap stated instead of implied.
31652df to
c21b637
Compare
Replace the parallel shortcut flow with a unified WorkflowRun revision model, strict five-node execution, history, quality gates, and non-blocking Playtest import. Preserve backend and provider boundaries without fabricated success states.
c3bd2dc to
a71e37e
Compare
| <Route path="/projects/:projectId" element={<ProjectDetailPage />} /> | ||
| <Route path="/workflow-editor/:runId" element={<WorkflowEditorPage />} /> | ||
| <Route path="/playtest/:characterId" element={<PlaytestPage />} /> | ||
| <Route path="/asset-library" element={<AssetLibraryPage />} /> |
There was a problem hiding this comment.
asset library 页面的内容是列出 character、action template、wearable 这些东西吗?是的话它们会被按项目组织吗?
There was a problem hiding this comment.
是的,资产库主体会列出当前项目下的 Character、自定义 ActionTemplate 和 Wearable,同时展示可供该项目使用的系统内置 ActionTemplate。既然内容按项目组织,我会将路由调整为 /projects/:projectId/assets,不再通过 /asset-library 的查询参数传递项目。
There was a problem hiding this comment.
对,现在的asset library 是按照项目组织的,一个 project 一个 asset library
There was a problem hiding this comment.
是的,资产库主体会列出当前项目下的 Character、自定义 ActionTemplate 和 Wearable,同时展示可供该项目使用的系统内置 ActionTemplate。既然内容按项目组织,我会将路由调整为
/projects/:projectId/assets,不再通过/asset-library的查询参数传递项目。
意思是,就直接把系统内置 ActionTemplate搬到项目里来用吗,那这里我觉得需要区分一下自定义 ActionTemplate和系统内置 ActionTemplate。因为他们两个不同,而 wearable和character 都没有这个系统内置一说
| * 复用能力一期之后做,先占好数据入口。 | ||
| */ | ||
|
|
||
| export interface ActionTemplate { |
There was a problem hiding this comment.
像“行走”、“待机”这样的非常典型的 action template,应该是会由我们的系统内置,而不需要用户手动去创建就能使用;那么它们会各自表现为一份 action template 数据吗?会的话它的 projectId 是什么?
There was a problem hiding this comment.
会,行走、待机 等内置动作也应作为 ActionTemplate 数据提供。当前强制要求 projectId 的模型不完整,然后的话我们会增加 system / project 作用域:系统模板的 projectId 为 null,项目自定义模板携带实际 projectId;按项目查询时返回两者的合集。
There was a problem hiding this comment.
补充一下 xyh 的评论,正如上一条所说,发现 ActionTemplate 的定义本身也有问题
模板本身其实只是提示词,但是现在带了previewImageUrl,注释写着「通常取第一帧」,这里模板没有图,填不出来
而真正需要的提示词又没有
那我们准备删除previewImageUrl、hasDisplacement。
然后增加一个真正能够用来约束 actiontemplate 的提示词,例如我们把他定义为 prompt
改完后就是这样:
export interface ActionTemplate {
id: string
/** 系统内置为 null,项目自定义带所属项目。 /
projectId: string | null
name: string
/* 生成这个动作用的提示词。 /
prompt: string
/* 一个动作循环几帧,属于动作定义,不是某次生成的统计。 */
frameCount: number
}
|
|
||
| export interface Frame { | ||
| /** 动作内序号。一个动作几帧由后端决定,前端不写死。 */ | ||
| index: number |
There was a problem hiding this comment.
会有这个很奇怪,一般来说拿到 frame 所属的 action,对比一下就能确定 frame 是第几个了,为啥需要表现为一个字段
There was a problem hiding this comment.
当前 Frame 只作为有序的 Action.frames 数组元素存在,index 与数组位置重复且可能产生不一致。我们会移除这个字段,审核和页面定位仍使用调用时计算出的 frameIndex;后续若 Frame 需要独立传输,再定义稳定的 ID 或排序字段。
There was a problem hiding this comment.
对,是多余的,定位会继续用调用时算的 frameIndex
| name: string | ||
| kind: ActionKind | ||
| status: ActionStatus | ||
| /** 播放时是否位移:跳跃有,待机、蹲下没有。由后端按动作标签给出。 */ |
There was a problem hiding this comment.
需要知道一个 action “是否位移”是为了什么?仅仅知道“是否”够吗?
There was a problem hiding this comment.
原意是让预览台判断动作是否包含根位移,但目前没有实际消费者,而且仅用布尔值无法表达方向、距离和逐帧偏移。这个字段属于过早预留,我会先从 Action 和 ActionTemplate 中移除;后续接入位移播放时再定义完整的运动数据契约。
There was a problem hiding this comment.
ok 这个字段会在下一次更新删除
| status: ActionStatus | ||
| /** 播放时是否位移:跳跃有,待机、蹲下没有。由后端按动作标签给出。 */ | ||
| hasDisplacement: boolean | ||
| frames: Frame[] |
There was a problem hiding this comment.
不会在前端全局写死,这里是类型契约遗漏。fps 应作为 Action 的播放元数据由后端返回,预览和导出均使用该值;MVP 即使采用统一默认帧率,也会通过 Action 数据明确传递。
There was a problem hiding this comment.
不写死,但是现在Action 漏了这个字段,我们会补一个 fps 加在 Action 上,由后端给出,预览和导出都用它。
在 Action 中补充:
/** 播放帧率,由后端给出,前端不写死。 */
fps: number
| */ | ||
| export type TaskStatus = 'queued' | 'running' | 'succeeded' | 'failed' | ||
|
|
||
| export interface TaskEvent { |
There was a problem hiding this comment.
这里确实缺少完整的 Task 形状,TaskEvent 只能表示任务状态更新,不能替代任务快照。我会补充 Task 契约,并让任务创建、查询和事件订阅复用同一组基础字段;尚未与后端确定的产出结构继续保留为 unknown。
|
|
||
| /** 工作流上可以发生的事,不区分是用户点的还是 AI 推的。 */ | ||
| export type WorkflowCommand = | ||
| | { kind: 'generate-template'; description: string; referenceImageUrl?: string } |
There was a problem hiding this comment.
这里 generate-template 中的 template 是什么概念?
There was a problem hiding this comment.
这里旧版本的 template 指角色母版图,不是动作模板 ActionTemplate,名称存在歧义。后续重构已移除 generate-template / confirm-template 命令,当前工作流改为通过 asset 和 generation 节点表达。
There was a problem hiding this comment.
现在代码改后歧义还是在,如entities/character/index.ts 里 confirmTemplate 指的是母版图,隔七行的 addAction 参数 templateId 指的是动作模板,后端路径 /characters/{id}/template/confirm 也是母版那个意思
我们准备把 template 都加前缀,要不然都用 template 分不清。
修改后是:
动作模板
- ActionTemplate —— 动作模板本身,名字 + 提示词
- actionTemplateId —— addAction 的参数,原来叫 templateId
角色母版
- candidateCharacterTemplates —— 生成出来的 3 张候选图
- characterTemplateUrl —— 用户选定的那一张,原来叫 templateImageUrl
- confirmCharacterTemplate —— 确认选定,原来叫 confirmTemplate
基准帧是母版确认后展开出来的多方向帧,不用template ,保持叫 baseFrames。
There was a problem hiding this comment.
当前目录只承载 useAsync 及其状态类型,lib 的含义确实过泛。会将其调整为 shared/hooks,并同步更新引用和模块文档。
|
|
||
| export interface WorkflowEditorProps { | ||
| runId: WorkflowRun['id'] | ||
| focus?: WorkflowEditorFocus |
There was a problem hiding this comment.
这个 focus 的能力可能不适合通过 prop 控制,而更适合以 imperative handle method 的方式由外部调用
There was a problem hiding this comment.
已调整,原先的 focus prop 已移除。当前节点位置由 URL 和外层 Page 管理,WorkflowEditor 只接收解析后的 activeType。暂时没有需要命令式调用的聚焦能力;后续若增加 action/frame 的滚动或聚焦,再通过精简的 imperative handle 暴露。
| */ | ||
| export interface InspectionPreviewProps { | ||
| characterId: Character['id'] | ||
| onOpenWorkflowAtFrame: (location: WorkflowLocation) => void |
There was a problem hiding this comment.
不适合由 InspectionPreview 去提供完整的 location;它给出它知道的 action、frame 信息就好,像 step id 这样的信息不应该由它来提供
There was a problem hiding this comment.
已调整:InspectionPreview 不再构造或回传完整的 WorkflowLocation。当前组件只负责核验展示和操作;后续接入真实播放器时,也只会上报自身知道的 action、frame 信息,其余工作流定位信息由页面层组合。
Workflow rules lived in entities and wrote straight to localStorage, so the frontend owned a state machine that belongs to the server. Add a mock workflow-run backend holding the DTO shape, storage, node advancement, quality gate and restart-revision logic, exposed over create, fetch and command routes, and register it in the mock dispatcher. Validation and state transitions now sit behind a network call; dropping the directory is all it takes once the real API lands.
createWorkflowRun, fetchWorkflowRun and submitWorkflowCommand each carried their own persistence and rule checks. Reduce them to request() calls with DTO translation, add the mapper module, and delete the local store. The public interface is unchanged, so pages and features keep compiling untouched.
The AI entry navigated to the workflow editor as soon as a run was created, which contradicts the agreed flow where the two entries never hand off to each other. Render the run status and generation panel inline, and offer the editor as a link the user chooses to follow. The route stays at /quick-start after submission; the flow test asserts that again.
The adapters README still described a localStorage adapter owned by entities. Point it at the mock backend that now stands in for the server. The directory keeps its placeholder role for real backend differences.
Keep AI-assisted creation in a focused route while sharing the existing workflow state underneath.
Document the public API facade, real route coverage, and the current Quick Start completion limits.
Keep the architecture status aligned with the current API boundary implementation.
Include the existing editor component and its interaction tests in the architecture tree.
明确公开契约的字段归属、单位、空值语义及前后端边界。
统一 ActionTemplate、characterTemplate 和 baseFrames 术语,并同步公开契约与架构文档。
The add-action input typed kind and actionTemplateId independently, so a caller could declare a preset action and leave out the template it was supposed to use. Nothing rejected that combination. Turn the input into a named discriminated union: the preset branch carries actionTemplateId, the custom branch carries none. Export AddActionInput from the entities facade so the shape is part of the public contract. The contradictory combination no longer type-checks.
…r one Two fields hold an uploaded image and both read like "the reference picture", which invites callers to reach for the wrong one. Say that sampleImageUrl constrains the visual style of every character in the project, and point at CreateCharacterInput.referenceImageUrl for the per-character image. Readers can tell the two layers apart without tracing call sites.
The previous assertion inlined the loose input shape, so loosening the union again would still satisfy it. Point the signature assertion at AddActionInput and add a case proving only the preset branch carries actionTemplateId. Reintroducing an optional template id now fails the contract suite.
The type was named CharacterVariant while the product, the review thread on 1024XEngineer#59, and the existing features/character-setup/outfit module all call the concept an outfit. Two names for one thing invites reading them as two layers. Rename the type to Outfit and follow through on its uses: Character.outfits, Action.outfitId, and the id parameters of confirmCharacterTemplate and addAction. The code now uses the same word as the product and the review.
The contract suite still asserted CharacterVariant, Character.variants and Action.variantId. Point every assertion at Outfit, outfits and outfitId. The suite matches the renamed contract.
API_CONTRACT, MODULES and the architecture note still described a variants layer, which no longer exists under that name. Replace the CharacterVariant and variants wording with Outfit and outfits. The documents match the types they describe.
The type was named CharacterVariant while the product, the review thread on 1024XEngineer#59, and the existing features/character-setup/outfit module all call the concept an outfit. Two names for one thing invites reading them as two layers. Rename the type to Outfit and follow through on its uses: Character.outfits, Action.outfitId, and the id parameters of confirmCharacterTemplate and addAction. The code now uses the same word as the product and the review.
Document the approved async repository boundary and the storage, ID, status, and validation safeguards before implementation.
Use an asynchronous repository port and one composition entry so a future adapter can be selected without changing orchestration callers.
Keep session memory authoritative over stale localStorage snapshots so failed persistence cannot hide newly created or updated workflows.
Generate local workflow IDs with random bytes or a session sequence when randomUUID is missing, including non-secure browser contexts.
Represent manual asset preparation and asset-level restarts separately from active generation so the UI no longer reports work that has not begun.
Validate complete run, revision, node, status, and reference shapes per record so corrupt localStorage data cannot reach consumers or hide valid siblings.
Synchronize the async composition boundary, resilient local persistence, ID fallback, generation lifecycle, and hydration validation across frontend documentation.
Run deterministic install, formatting, lint, type checks, tests, and production build for pull requests and main updates.
6d01557 to
437fb8d
Compare
Create a real project before starting an AI workflow, keep AI and manual runs on the same node sequence, and define an injectable image-generation port with production mock safeguards. Synchronize architecture tests and contract documentation with the new boundary.
Separate frontend entities, callable capabilities, user-facing features, composition, and transport responsibilities before moving the currently misclassified modules.
Record the full frontend audit, including Project repository composition, cross-entity correlation cleanup, shared pagination ownership, and removal of unused compatibility entries.
Break the full frontend audit into test-driven migrations for capabilities, entities, Project composition, shared cleanup, architecture enforcement, and documentation.
改动说明
落地 #58 的前端初始骨架:目录结构、模块边界与各自对外接口。空的是实现不是接口——函数签名、参数与返回类型已定,后端据此可推出需要提供哪些接口。
模块清单见
frontend/MODULES.md,共四组:entities数据模块、pages/workflow-editor/editor、pages/playtest/inspection-preview,以及AppShell与PageHeader两个公开 UI 组件。文档同时写明分层不等于模块,pages / features / shared整层不是模块。实现方式
entities/index.ts是数据层唯一入口,45 项公开 export;Project 已对接后端 feat:add project model #57 的真实字段与路径,Character、WorkflowRun、ActionTemplate、Wearable 标为提案。react-router,脱离路由可渲染可测试;路由适配留在各自 Page。shared/api下 real / mock / mappers 三份实现对外一致,VITE_USE_MOCK切换。entities/workflow-run/api/下的实现。与 #45 文档的两处出入
MODULES.md写明是有意修订。测试
npm run typecheck:通过npm run lint(oxlint):通过npm test:29 passed(7 个测试文件)npm run build:通过依赖边界由
tests/integration/architecture.test.ts用 TypeScript AST 解析每一条 import 自动检查(分层方向、同层隔离、模块入口、循环依赖)。已实测插入越权 import 会导致测试失败。未冻结项
Character / Action / Frame / WorkflowRun / TaskEvent 的后端 Schema、单帧回改链、检查台真实播放与画布实现均不在本 PR 内,
MODULES.md有清单。entities 接口在前后端共同走查前按提案对待,不作为已定契约。Closes #58
Refs #45 #51