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 重新挂载),并补一个抛错后导航到正常页面的测试。
| export async function submitWorkflowStep( | ||
| runId: string, | ||
| stepId: string, | ||
| _command: WorkflowCommand, |
There was a problem hiding this comment.
[P1] 提交的命令被完全丢弃,公开的工作流状态机无法实际运行。 这里不校验命令是否匹配当前步骤,也不根据 add-action / generate-action 等命令追加或更新步骤;初始两步完成后 next 为空,运行会直接变成 completed,因此 selectors 宣称支持的加动作、生成、退回与 finish 流程都无法通过此 API 到达。请让 transition 消费 command(并校验 stepId/currentStepId),或在实现前不要把它作为可工作的提交接口暴露。
|
|
||
| const commands: WorkflowCommandKind[] = ['add-action'] | ||
| // 有已加未生成的动作才能发起生成 | ||
| if (added.length > generated.length) commands.push('generate-action') |
There was a problem hiding this comment.
[P2] 用步骤数量判断是否有待生成动作会丢失 action 身份。 例如添加 action 10/20、但 action 10 被生成两次时,两边长度相等,这里会禁止 generate-action,尽管 action 20 仍未生成;生成了一个未添加的 action 也会产生同样误判。应按 actionId 集合/每个动作的状态判断,并覆盖重复生成与不同 action 的测试。
| const form = new FormData() | ||
| form.append('file', file) | ||
|
|
||
| const response = await fetch(`${import.meta.env.VITE_API_BASE_URL ?? '/api'}/upload/image`, { |
There was a problem hiding this comment.
[P2] 图片上传绕过了 real/mock 选择。 项目其余 API 都受 VITE_USE_MOCK 控制,但这里始终直接请求 /api/upload/image;默认 mock 模式且未启动后端时,这个公开接口仍会网络失败,与 README 的“不用起后端也能开发”不一致。请为 multipart 上传提供同一选择入口/明确 mock 实现,或收窄文档与公开契约。
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. Co-Authored-By: Codex <noreply@openai.com>
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 这些东西吗?是的话它们会被按项目组织吗?
| * 复用能力一期之后做,先占好数据入口。 | ||
| */ | ||
|
|
||
| export interface ActionTemplate { |
There was a problem hiding this comment.
像“行走”、“待机”这样的非常典型的 action template,应该是会由我们的系统内置,而不需要用户手动去创建就能使用;那么它们会各自表现为一份 action template 数据吗?会的话它的 projectId 是什么?
|
|
||
| export interface Frame { | ||
| /** 动作内序号。一个动作几帧由后端决定,前端不写死。 */ | ||
| index: number |
There was a problem hiding this comment.
会有这个很奇怪,一般来说拿到 frame 所属的 action,对比一下就能确定 frame 是第几个了,为啥需要表现为一个字段
| name: string | ||
| kind: ActionKind | ||
| status: ActionStatus | ||
| /** 播放时是否位移:跳跃有,待机、蹲下没有。由后端按动作标签给出。 */ |
There was a problem hiding this comment.
需要知道一个 action “是否位移”是为了什么?仅仅知道“是否”够吗?
| status: ActionStatus | ||
| /** 播放时是否位移:跳跃有,待机、蹲下没有。由后端按动作标签给出。 */ | ||
| hasDisplacement: boolean | ||
| frames: Frame[] |
| */ | ||
| export type TaskStatus = 'queued' | 'running' | 'succeeded' | 'failed' | ||
|
|
||
| export interface TaskEvent { |
|
|
||
| /** 工作流上可以发生的事,不区分是用户点的还是 AI 推的。 */ | ||
| export type WorkflowCommand = | ||
| | { kind: 'generate-template'; description: string; referenceImageUrl?: string } |
There was a problem hiding this comment.
这里 generate-template 中的 template 是什么概念?
|
|
||
| export interface WorkflowEditorProps { | ||
| runId: WorkflowRun['id'] | ||
| focus?: WorkflowEditorFocus |
There was a problem hiding this comment.
这个 focus 的能力可能不适合通过 prop 控制,而更适合以 imperative handle method 的方式由外部调用
| */ | ||
| export interface InspectionPreviewProps { | ||
| characterId: Character['id'] | ||
| onOpenWorkflowAtFrame: (location: WorkflowLocation) => void |
There was a problem hiding this comment.
不适合由 InspectionPreview 去提供完整的 location;它给出它知道的 action、frame 信息就好,像 step id 这样的信息不应该由它来提供
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.
改动说明
落地 #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