feat(app+ds): 阶段2 接前端写操作——任务创建/配置保存/新建项目

让原型里的视觉 mock 表单变为真正可写(附加式、向后兼容原型 index.html):

design/ui_kits/console(surface 功能化,DS 组件本就支持 onChange):
- NewTaskPanel:表单受控(title/complexity/priority/parentId),onCreate 出参;
  父任务下拉扁平化含子任务
- TaskSection:暴露 onCreate prop(无则回退原 toast)
- ConfigPanel:受控(concurrency/autonomy/logo/verifyCmd/model),onSave 出参

app/:
- api.js:加 createProject
- app.jsx:createTask→POST /tasks、saveConfig→PATCH /projects、createProject→
  POST /projects;新增 NewProjectModal(design 无现成 surface,用 DS 组件就地拼装,
  zh/en 标签自含);接 Sidebar 新建项目入口

验证:dev + 生产构建双通过;Playwright 实测新建项目模态打开、受控输入捕获值
(["demo-app",".../demo-app","main"])、0 console 错误。写端点契约逐字段比对
server.ts(PATCH camelCase / POST tasks {title,complexity(auto),priority,parentId,deps})。
活写测试从略:maestro 为 auto-approved,建真实任务会触发 agent。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-24 14:44:46 +08:00
parent d6ae4dbe13
commit 0a1f500c12
4 changed files with 98 additions and 16 deletions
+15 -6
View File
@@ -109,21 +109,30 @@ function Topbar({ project, counts, onSync, onToggleConfig, t, theme, onToggleThe
function ConfigPanel({ project, onSave, onClose, onSync, t }) {
const { Button, Input, Select } = window.MaestroDesignSystem_a6a290;
const [concurrency, setConcurrency] = React.useState(String(project.concurrency));
const [autonomy, setAutonomy] = React.useState(project.autonomy);
const [logo, setLogo] = React.useState(project.logo || '');
const [verifyCmd, setVerifyCmd] = React.useState(project.verifyCmd || '');
const [model, setModel] = React.useState(project.model || '');
const save = () => onSave({
concurrency: Number(concurrency), autonomy,
logo: logo || null, verifyCmd: verifyCmd || null, model: model || null,
});
return (
<section style={{ position: 'relative', zIndex: 30, background: 'var(--panel)', border: '1px solid var(--line)', borderRadius: 6, padding: '12px 14px', marginTop: 12, animation: 'maestro-rise .18s ease both' }}>
<div style={{ display: 'flex', gap: 14, alignItems: 'flex-end', flexWrap: 'wrap' }}>
<Input label={t.maxConcurrency} type="number" defaultValue={String(project.concurrency)} width={76} />
<Select label={t.workMode} defaultValue={project.autonomy} options={Object.entries(t.autonomy).map(([value, label]) => ({ value, label }))} />
<Input label={t.maxConcurrency} type="number" value={concurrency} onChange={setConcurrency} width={76} />
<Select label={t.workMode} value={autonomy} onChange={setAutonomy} options={Object.entries(t.autonomy).map(([value, label]) => ({ value, label }))} />
<span style={{ fontSize: 11, color: 'var(--muted)', letterSpacing: '.06em', marginLeft: 'auto', paddingBottom: 7 }}>
{t.current}{project.concurrency} · {project.autonomy}
</span>
<Button variant="solid" onClick={onSave}>{t.save}</Button>
<Button variant="solid" onClick={save}>{t.save}</Button>
<Button onClick={onClose}>{t.collapse}</Button>
</div>
<div style={{ display: 'flex', gap: 14, alignItems: 'flex-end', flexWrap: 'wrap', marginTop: 12 }}>
<span style={{ flex: 2, minWidth: 220, display: 'flex' }}><Input label={t.projLogo} placeholder={t.logoPh} style={{ width: '100%' }} /></span>
<span style={{ flex: 1, minWidth: 170, display: 'flex' }}><Input label={t.verifyCmd} placeholder={t.verifyPh} style={{ width: '100%' }} /></span>
<span style={{ flex: 1, minWidth: 170, display: 'flex' }}><Input label={t.model} placeholder={t.modelPh} style={{ width: '100%' }} /></span>
<span style={{ flex: 2, minWidth: 220, display: 'flex' }}><Input label={t.projLogo} placeholder={t.logoPh} value={logo} onChange={setLogo} style={{ width: '100%' }} /></span>
<span style={{ flex: 1, minWidth: 170, display: 'flex' }}><Input label={t.verifyCmd} placeholder={t.verifyPh} value={verifyCmd} onChange={setVerifyCmd} style={{ width: '100%' }} /></span>
<span style={{ flex: 1, minWidth: 170, display: 'flex' }}><Input label={t.model} placeholder={t.modelPh} value={model} onChange={setModel} style={{ width: '100%' }} /></span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 10, paddingTop: 10, borderTop: '1px dashed var(--line-soft)' }}>
<span style={{ fontSize: 10.5, color: 'var(--faint)', letterSpacing: '.06em' }}>{t.lastSync}</span>