Skip to content

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

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

feat(frontend): add MS2 skeleton with module contracts#59
huyanxius wants to merge 51 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 重新挂载),并补一个抛错后导航到正常页面的测试。

Comment thread frontend/src/entities/workflow-run/api/submit-workflow-step.ts Outdated
Comment thread frontend/src/entities/workflow-run/model/selectors.ts Outdated
Comment thread frontend/src/entities/project/api.ts Outdated
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.
@xyh202131
xyh202131 force-pushed the feat/58-frontend-skeleton branch from c3bd2dc to a71e37e Compare July 27, 2026 11:10
Comment thread frontend/src/app/index.tsx Outdated
<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 这些东西吗?是的话它们会被按项目组织吗?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

是的,资产库主体会列出当前项目下的 Character、自定义 ActionTemplate 和 Wearable,同时展示可供该项目使用的系统内置 ActionTemplate。既然内容按项目组织,我会将路由调整为 /projects/:projectId/assets,不再通过 /asset-library 的查询参数传递项目。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

对,现在的asset library 是按照项目组织的,一个 project 一个 asset library

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

是的,资产库主体会列出当前项目下的 Character、自定义 ActionTemplate 和 Wearable,同时展示可供该项目使用的系统内置 ActionTemplate。既然内容按项目组织,我会将路由调整为 /projects/:projectId/assets,不再通过 /asset-library 的查询参数传递项目。

意思是,就直接把系统内置 ActionTemplate搬到项目里来用吗,那这里我觉得需要区分一下自定义 ActionTemplate和系统内置 ActionTemplate。因为他们两个不同,而 wearable和character 都没有这个系统内置一说

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

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 是什么?

@xyh202131 xyh202131 Jul 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

会,行走待机 等内置动作也应作为 ActionTemplate 数据提供。当前强制要求 projectId 的模型不完整,然后的话我们会增加 system / project 作用域:系统模板的 projectIdnull,项目自定义模板携带实际 projectId;按项目查询时返回两者的合集。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

补充一下 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

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 是第几个了,为啥需要表现为一个字段

@xyh202131 xyh202131 Jul 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

当前 Frame 只作为有序的 Action.frames 数组元素存在,index 与数组位置重复且可能产生不一致。我们会移除这个字段,审核和页面定位仍使用调用时计算出的 frameIndex;后续若 Frame 需要独立传输,再定义稳定的 ID 或排序字段。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

对,是多余的,定位会继续用调用时算的 frameIndex

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 “是否位移”是为了什么?仅仅知道“是否”够吗?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

原意是让预览台判断动作是否包含根位移,但目前没有实际消费者,而且仅用布尔值无法表达方向、距离和逐帧偏移。这个字段属于过早预留,我会先从 ActionActionTemplate 中移除;后续接入位移播放时再定义完整的运动数据契约。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

ok 这个字段会在下一次更新删除

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 是全局写死的吗

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

不会在前端全局写死,这里是类型契约遗漏。fps 应作为 Action 的播放元数据由后端返回,预览和导出均使用该值;MVP 即使采用统一默认帧率,也会通过 Action 数据明确传递。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

不写死,但是现在Action 漏了这个字段,我们会补一个 fps 加在 Action 上,由后端给出,预览和导出都用它。
在 Action 中补充:
/** 播放帧率,由后端给出,前端不写死。 */
fps: number

*/
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 形状的定义吗

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

这里确实缺少完整的 Task 形状,TaskEvent 只能表示任务状态更新,不能替代任务快照。我会补充 Task 契约,并让任务创建、查询和事件订阅复用同一组基础字段;尚未与后端确定的产出结构继续保留为 unknown

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已补全


/** 工作流上可以发生的事,不区分是用户点的还是 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

Choose a reason for hiding this comment

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

这里旧版本的 template 指角色母版图,不是动作模板 ActionTemplate,名称存在歧义。后续重构已移除 generate-template / confirm-template 命令,当前工作流改为通过 assetgeneration 节点表达。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

现在代码改后歧义还是在,如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。

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 怪怪的

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

当前目录只承载 useAsync 及其状态类型,lib 的含义确实过泛。会将其调整为 shared/hooks,并同步更新引用和模块文档。

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 的方式由外部调用

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

已调整,原先的 focus prop 已移除。当前节点位置由 URL 和外层 Page 管理,WorkflowEditor 只接收解析后的 activeType。暂时没有需要命令式调用的聚焦能力;后续若增加 action/frame 的滚动或聚焦,再通过精简的 imperative handle 暴露。

*/
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 这样的信息不应该由它来提供

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

已调整:InspectionPreview 不再构造或回传完整的 WorkflowLocation。当前组件只负责核验展示和操作;后续接入真实播放器时,也只会上报自身知道的 action、frame 信息,其余工作流定位信息由页面层组合。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已修改

huyan and others added 8 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.
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 术语,并同步公开契约与架构文档。
huyan added 3 commits July 28, 2026 15:30
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.
huyan added 3 commits July 28, 2026 15:46
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.
huyanxius pushed a commit to huyanxius/Windup that referenced this pull request Jul 28, 2026
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.
@xyh202131
xyh202131 force-pushed the feat/58-frontend-skeleton branch from 6d01557 to 437fb8d Compare July 28, 2026 07:57
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.
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 初始骨架:目录结构、模块接口与依赖边界

4 participants