Skip to content

feat(frontend): add MS2 skeleton with module contracts#59

Open
huyanxius wants to merge 15 commits into
1024XEngineer:mainfrom
huyanxius:feat/58-frontend-skeleton
Open

feat(frontend): add MS2 skeleton with module contracts#59
huyanxius wants to merge 15 commits into
1024XEngineer:mainfrom
huyanxius:feat/58-frontend-skeleton

Conversation

@huyanxius

Copy link
Copy Markdown
Collaborator

改动说明

落地 #58 的前端初始骨架:目录结构、模块边界与各自对外接口。空的是实现不是接口——函数签名、参数与返回类型已定,后端据此可推出需要提供哪些接口。

模块清单见 frontend/MODULES.md,共四组:entities 数据模块、pages/workflow-editor/editorpages/playtest/inspection-preview,以及 AppShellPageHeader 两个公开 UI 组件。文档同时写明分层不等于模块,pages / features / shared 整层不是模块。

实现方式

  • entities/index.ts 是数据层唯一入口,45 项公开 export;Project 已对接后端 feat:add project model #57 的真实字段与路径,Character、WorkflowRun、ActionTemplate、Wearable 标为提案。
  • 两个页面内模块有明确 props,且不 import react-router,脱离路由可渲染可测试;路由适配留在各自 Page。
  • shared/api 下 real / mock / mappers 三份实现对外一致,VITE_USE_MOCK 切换。
  • 后端当前不存 workflow,工作流状态先落 localStorage,对外签名按将来走网络的形状设计,后端接管时只替换 entities/workflow-run/api/ 下的实现。

#45 文档的两处出入

  • 架构文档第 3 节「同一层的不同 Slice 不能互相 import」对 entities 不再适用:既然整个 entities 是一个模块,内部分区可互引,对外仍只有一个入口。依赖检查据此只对 pages、features 强制同层隔离。已在 MODULES.md 写明是有意修订。
  • 第 10 节骨架竖线写的是「shared/api/client 使用 mock 返回 runId」,但工作流不经 api 层,runId 由本地存储生成。竖线本身已通。

测试

  • 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

huyan added 6 commits July 27, 2026 15:02
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.

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

发现 4 项需要处理的行为/契约问题,已放在对应 diff 行内。本地验证通过:npm test(29 tests)、npm run typechecknpm run lintnpm run build

View job run


render(): ReactNode {
const { error } = this.state
if (!error) return this.props.children

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 路由切换无法清除错误态。 一旦任意页面触发该边界,error 会一直保留;顶部导航虽然仍可点击,但 <Routes> 换成新页面后这里仍只返回 fallback,与上方“用户能自己走开”的契约相反。请在 location 变化时重置边界(例如由路由适配层按 pathname/key 重新挂载),并补一个抛错后导航到正常页面的测试。

export async function submitWorkflowStep(
runId: string,
stepId: string,
_command: WorkflowCommand,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 用步骤数量判断是否有待生成动作会丢失 action 身份。 例如添加 action 10/20、但 action 10 被生成两次时,两边长度相等,这里会禁止 generate-action,尽管 action 20 仍未生成;生成了一个未添加的 action 也会产生同样误判。应按 actionId 集合/每个动作的状态判断,并覆盖重复生成与不同 action 的测试。

Comment thread frontend/src/entities/project/api.ts Outdated
const form = new FormData()
form.append('file', file)

const response = await fetch(`${import.meta.env.VITE_API_BASE_URL ?? '/api'}/upload/image`, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 图片上传绕过了 real/mock 选择。 项目其余 API 都受 VITE_USE_MOCK 控制,但这里始终直接请求 /api/upload/image;默认 mock 模式且未启动后端时,这个公开接口仍会网络失败,与 README 的“不用起后端也能开发”不一致。请为 multipart 上传提供同一选择入口/明确 mock 实现,或收窄文档与公开契约。

huyan added 4 commits July 27, 2026 16:08
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.
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>
@xyh202131
xyh202131 force-pushed the feat/58-frontend-skeleton branch from c3bd2dc to a71e37e Compare July 27, 2026 11:10
<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 />} />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asset library 页面的内容是列出 character、action template、wearable 这些东西吗?是的话它们会被按项目组织吗?

* 复用能力一期之后做,先占好数据入口。
*/

export interface ActionTemplate {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

像“行走”、“待机”这样的非常典型的 action template,应该是会由我们的系统内置,而不需要用户手动去创建就能使用;那么它们会各自表现为一份 action template 数据吗?会的话它的 projectId 是什么?


export interface Frame {
/** 动作内序号。一个动作几帧由后端决定,前端不写死。 */
index: number

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

会有这个很奇怪,一般来说拿到 frame 所属的 action,对比一下就能确定 frame 是第几个了,为啥需要表现为一个字段

name: string
kind: ActionKind
status: ActionStatus
/** 播放时是否位移:跳跃有,待机、蹲下没有。由后端按动作标签给出。 */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

需要知道一个 action “是否位移”是为了什么?仅仅知道“是否”够吗?

status: ActionStatus
/** 播放时是否位移:跳跃有,待机、蹲下没有。由后端按动作标签给出。 */
hasDisplacement: boolean
frames: Frame[]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

一个 action 的 fps 是全局写死的吗

*/
export type TaskStatus = 'queued' | 'running' | 'succeeded' | 'failed'

export interface TaskEvent {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

没有 Task 形状的定义吗


/** 工作流上可以发生的事,不区分是用户点的还是 AI 推的。 */
export type WorkflowCommand =
| { kind: 'generate-template'; description: string; referenceImageUrl?: string }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里 generate-template 中的 template 是什么概念?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: 这个目录叫 lib 怪怪的

Comment thread frontend/MODULES.md Outdated

export interface WorkflowEditorProps {
runId: WorkflowRun['id']
focus?: WorkflowEditorFocus

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个 focus 的能力可能不适合通过 prop 控制,而更适合以 imperative handle method 的方式由外部调用

*/
export interface InspectionPreviewProps {
characterId: Character['id']
onOpenWorkflowAtFrame: (location: WorkflowLocation) => void

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不适合由 InspectionPreview 去提供完整的 location;它给出它知道的 action、frame 信息就好,像 step id 这样的信息不应该由它来提供

huyan added 4 commits July 27, 2026 22:01
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

前端 MS2 初始骨架:目录结构、模块接口与依赖边界

3 participants