feat(project-create): add the project creation page - #142
Merged
nighca merged 15 commits intoAug 7, 2026
Conversation
Most backend write endpoints carry user_id in the request body instead of deriving it from the token, so pages need the signed-in user id on its own. Register and read it through the same lazy provider pattern already used for the access token, returning null when no login module is mounted. Callers can now require a real user id before writing, without falling back to a constant placeholder.
Nothing in the app could create a project: the entry existed only as a disabled button and a link to the project list. Add /projects/new with the fields the Project contract defines, submitting through ProjectApis and reporting business failures with the reason the backend gives. Users reach a real creation form, and the page keeps itself closed while no signed-in user is available.
The home canvas card and the project center both advertised project creation while leading somewhere that could not create anything. Send the home canvas action and the project center button to /projects/new, and rewrite the empty state copy that referred to a flow living elsewhere. Creation now has one destination reachable from either place.
Creation crosses the page, the entity adapter and the HTTP envelope, so the mapped request body and the failure paths need to be pinned down. Teach the shared test backend to answer POST /projects including the duplicate name rejection, then assert the payload, the guards and both entry links. Regressions in the field mapping, the submit guard or the entry points now fail the suite.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The page sets its own padding instead of using PageContainer, and below the lg breakpoint that padding was shorter than the fixed header, so the eyebrow and heading sat underneath it. Raise the top padding to the same value PageContainer reserves for the header, and note where the number comes from. The heading clears the header at every width instead of only on wide screens.
The submit handler read the submitting flag out of its own render closure, so within one batch of events the second submit still saw the stale value and only the disabled attribute stopped it. Hold the in-flight state in a ref and check that before doing any work, releasing it when the request fails. The guard the handler claims to have is the one that actually runs.
The error banner stayed on screen while the user corrected the field it complained about, so the page kept accusing them of something they had already fixed. Clear it from a single change handler on the form, which every control inside already bubbles to. The banner describes the current state of the form rather than the last attempt.
A disabled button takes no focus, so the sentence explaining why creation is closed sat next to it with nothing linking the two. Give the sentence an id and point the button at it with aria-describedby. Assistive technology reaches the reason from the control it applies to.
The comments on the field limits pointed at a ProjectCreate schema and a relaxed name length that exist only in unmerged backend PRs, so a reader grepping the repository finds nothing. Name what main actually carries, mark the rest as pending, and record that the style cap is a frontend decision rather than a contract. Each limit can be traced to something a reader can open.
Only the duplicate-name failure had a test, leaving the empty name, the out-of-range sprite size, the transport error message and the error dismissal unproven. Add four cases asserting each guard blocks the request or reports the expected sentence. The validation branches the page carries are all exercised.
The contract listed the creation flow among the things this round would not implement, which stopped being true, and said nothing about where user_id comes from. Move the note into the Project section and state that a missing current-user source keeps the entry disabled rather than substituting a placeholder. The document matches what the frontend now does.
The banner is dismissed from a change handler on the form, but the size presets are buttons that write the width and height in code, so clicking one fixed the value and left the complaint on screen. Clear the error inside the preset handler as well, and cover the path with a test. The fastest way to correct a size no longer leaves a stale message behind.
The backend moved project ownership out of the request body: ProjectCreate no longer declares user_id and every /projects route reads request.state.current_user.id, which closed a hole where a caller could create a project for someone else. Drop ownerId from CreateProjectInput, stop sending user_id, and gate the create page on getApiAccessToken() instead of a current user id. The entry now opens on the condition the backend actually enforces, and its disabled hint says a sign-in is required.
The provider existed only because POST /projects used to take user_id in its body; with ownership read from the token nothing consumes it. Remove the CurrentUserIdProvider type, its registration function and getCurrentUserId. The module keeps one boundary for the auth module to fill instead of two, and no caller can mistake a user id for proof of a session.
The Project section still described user_id travelling in the create body and pointed at a helper that no longer exists. Rewrite that paragraph around the token-derived ownership and the sign-in gate. The document matches the contract the code now targets.
This was referenced Aug 6, 2026
nighca
approved these changes
Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
新增
/projects/new,补上 #99 里唯一还没落地的那一段:项目创建。Closes #141
Why
main上没有任何地方能创建项目。首页「工作流画布」卡片的两条动作都落在/projects,pages/home/index.tsx:88的注释写明这是占位;项目中心右上角的「+ 新建项目」从 #119 加进来那天起就是disabled,title写着「新建项目流程不在本模块中」。用户看得到项目列表、能删项目,唯独建不出项目——这条链的第一步是断的。#99 把列表、创建、详情放在一起,其中列表与详情已由 #118 落地,剩下创建这一段没人接。
Change Description
pages/project-create/,源码 2 个文件、测试 1 个文件;app.tsx加一条路由,shared/api加一个当前用户读取边界,index.css加装饰点阵的 keyframes。/projects/new,一份实现两个入口。「继续已有项目」本次不动,仍落项目中心。disabled换成链接;空状态里「完成新建项目流程后,项目会显示在这里」改成指向右上角的实话。一轮独立验收(只看 diff 与 issue,不看本文)后修掉的:
PageContainer,自己写的顶部留白在 lg 断点以下只有 48px,而 fixed 顶栏下沿在 72px,标题被压在底下。已按PageContainer同源的pt-24让位。submit里读的是本次 render 闭包中的submitting,同一批事件里它还是旧值,真正拦住第二次的是按钮的disabled。已改成 ref latch,handler 声称有的那道闸真的会拦。aria-describedby;三处常量注释把出处指向未合入 PR 里的代码(grep不到);API_CONTRACT.md仍写着「新建项目流程:只保留禁用入口」。均已修。Implementation Approach
CreateProjectInput,不照 live demo。 视觉参考了 asset-lab 那版「新建角色项目」,但它是 mock:「启动方式」在架构里没有对应物(feat: 实现单角色 Workflow Editor 卡片画布 #120 已把 Workflow 模板复用划到范围外),名称限 48 字,尺寸只有三档正方形的单选。这里只保留契约里真有的六个字段,尺寸拆成宽高两个数字输入(32–2048,取自 issue feat: 实现项目创建页 /projects/new(Refs #99) #141 的产品规则,与未合入的 feat(backend): 后端全栈实现——基础设施 + 领域模块 + 用户认证 + API 契约 #75 / feat: integrate verified Windup source snapshot #126 请求校验一致),三档退成快捷填充。ProjectCreate写的是max_length=20,与main上project/model.py的String(20)一致;feat: integrate verified Windup source snapshot #126 放宽到 64 并同时改了 model。按 20 实现在两种契约下都能提交成功。后端定稿后放宽是加法,反过来是破坏。POST /projects把user_id放在请求体里,不从 token 推导,而main上没有任何「当前用户是谁」的来源。新增registerCurrentUserIdProvider/getCurrentUserId,照getApiAccessToken那套惰性 provider 的写法;没有人注册时返回null,页面禁用提交并在页面上写明原因,不塞常量假 ID 让它看起来能用。assertSuccessfulEnvelope把后端 message 原样带进ApiError(shared/api/index.ts:128),所以重名显示的是后端那句「项目名称已存在」,只有传输失败才收敛成「项目暂时无法创建」。两种情况都不清空已填内容。submitting在同一批事件里读到的是上一次 render 的值,挡不住;inFlightref 在submit入口同步判断并置位,请求失败时释放。(x+y) % 4相位算出,四组 keyframes 错峰循环。没有 canvas、没有 pointer 监听、不引任何依赖,prefers-reduced-motion下整体停掉。它画的是画布方框、四角把手和一支光标形状;那支光标是画出来的图形,不跟随真实鼠标。PageContainer。 这页左右满幅,全局顶栏保留:路由挂在AppShellRoute下,与/projects平级。Screenshots
Before —
main上的项目中心,「+ 新建项目」是灰的,title写着流程不在本模块中:Before — 首页画布卡片展开后的两条动作,此时都指向
/projects:After —
/projects/new,左侧点阵、右侧表单,顶栏保留:After — 重名时显示后端原话,已填内容不丢:
After — 创建成功后项目进入列表,「+ 新建项目」已是可点的入口:
截图取自本地 dev server,配一个只存在于本机、按 PR #75 的
ProjectCreate/ProjectOut形状回应的桩服务(业务错误照后端习惯走 HTTP 200 +code != 200)。为让创建入口可用,本机临时注册了一个返回固定user_id的 provider。桩服务与那行临时注册都不进仓库。Testing
本地执行了 CI 工作流中的全部步骤:
npm run format:check— 通过(58 个文件)npm run lint— 通过,无告警npm run typecheck— 通过npm run test— 通过(11 个文件 / 48 个用例,其中 project-create 9 个用例)npm run build— 通过(280.25 kB / gzip 87.98 kB)浏览器手工核对:两个入口都进
/projects/new;重名报错后表单内容保留;改名后提交成功并跳进该项目的资产工作区,侧栏规格与表单填的一致;未注册用户 provider 时提交按钮为灰并给出原因。Follow-ups
sprite_sample_url后端已有,但前端还没有文件上传能力(feat: 实现前端媒体上传适配与错误边界(Refs #70) #109 / feat(media): add validated upload adapter #111)。严格照契约做出来会是一个「请粘贴图片链接」的输入框,产品上是负分,因此本 PR 有意不放这个字段,上传适配落地后单独提 PR 补。/projects/:projectId/history。/workflow-editor/:runId需要一个 runId,而WorkflowController在main上只有接口没有实现(PR feat(workflow-controller): coordinate one workflow run #107),编辑器本身也还是占位页(feat: 实现单角色 Workflow Editor 卡片画布 #120)。现在创建成功后落该项目的资产工作区。/projects与/auth都还没进main(feat(backend): 后端全栈实现——基础设施 + 领域模块 + 用户认证 + API 契约 #75 / feat: integrate verified Windup source snapshot #126)。接口挂载前,部署环境里这页的创建入口是禁用状态。project_name限长不一致(feat(backend): 后端全栈实现——基础设施 + 领域模块 + 用户认证 + API 契约 #75 是 20,feat: integrate verified Windup source snapshot #126 是 64),需要定一个。另外POST /projects在请求体里收user_id而不是从登录态取,在已有 JWT 与/auth/me的前提下意味着可以替别人建项目,这条也值得后端确认。Related