Joye Dev

Back

feat: layout import steps from extracted-content flags

**PR:**feat: layout import steps from extracted-content flags (#4651)


正文来源:飞书学习文档。以下为通过个人 Feishu API 获取并转换后的完整 Markdown 正文。

今日选择#

**PR:**feat: layout import steps from extracted-content flags (#4651)

**作者:**Yuxing Wang / ongyuxing

**Merge 时间:**2026-07-11T15:03:28Z(Australia/Melbourne 2026-07-12 01:03:28)

链接:https://github.com/adastralab-ai/voyager/pull/4651

**模块:**idl/typespec/services/task.tsp;backend/go/internal/brand/task_api.go;packages/site/src/app/(main)/brands/new;生成 API types/zod。

**学习标签:**brand-import、api-contract、task-result-shape、frontend-state-consistency、codegen-boundary、progress-ui。

为什么值得学#

  • 它把“进度动画应该展示哪些步骤”从前端猜测改成后端任务结果中的显式能力信号,避免 UI 在 finalize 前后使用两套不一致的真相来源。
  • 它没有把完整 draft 内容提前暴露给前端,只加 hasFonts、hasColors、hasGuidelines、hasImages 四个布尔位,保持任务结果的契约很窄。
  • 它按 Voyager 的 contract-first 路径修改 TypeSpec,再同步生成 Go/TS/zod 类型,让跨 Go 后端、site、admin、agent worker 的调用面一致。
  • 它保留 finalize 后从真实 BrandDetails 计算可见步骤的逻辑,因此初始阶段用 draft presence,最终阶段用持久化 brand 内容,两个阶段各自用最可靠的数据源。

关键代码#

1. TypeSpec 把 UI 需要的 presence 变成任务结果契约#

// A completed brand_import has no brand yet. The brand is created on finalize.
// Logos are the only draft content surfaced for review. The `has*` flags let
// the review UI lay out its steps before the brand exists. The section
// contents arrive with the finalized brand.
model BrandImportTaskResult {
  logos: BrandImportDraftLogo[];
  hasFonts: boolean;
  hasColors: boolean;
  hasGuidelines: boolean;
  hasImages: boolean;
plaintext

设计点:这里明确区分“draft review 阶段”和“finalized brand 阶段”。前端只需要知道某类内容是否存在,不需要拿到 guidelines/images/texts 的完整 draft,因此契约保持最小。

2. Go 转换层在已有 draft 上派生 flags#

hasColors := false
for _, p := range draft.Palettes {
    if len(p.Colors) > 0 {
        hasColors = true
        break
    }
}
...
Result: api.BrandImportTaskResult{
    Logos:         logos,
    HasFonts:      len(draft.Texts) > 0,
    HasColors:     hasColors,
    HasGuidelines: len(draft.Guidelines) > 0,
    HasImages:     len(draft.Images) > 0,
},
plaintext

设计点:presence 判断放在后端任务 API 的 projection 层,而不是前端或 finalize 接口里临时拼。颜色还特意检查 palette 内是否真的有 colors,避免仅有空 palette 就误报。

3. 前端把初始步骤映射成同一组 processSteps 的过滤器#

export const getInitialSteps = (result: BrandImportTaskResult) => {
  const present: Record<ProcessStepKey, boolean> = {
    fonts: result.hasFonts,
    colors: result.hasColors,
    guidelines: result.hasGuidelines,
    assets: result.hasImages,
  };
  return processSteps.filter((step) => present[step.key]);
};
plaintext

设计点:初始阶段没有复制一套步骤定义,只是给现有 processSteps 增加一个基于 task result 的过滤入口。这样文案、图标、渲染组件仍然集中在 ProcessStep 的同一套定义里。

4. 进度组件按阶段切换数据源#

const [logos, setLogos] = useState(draft.logos);
...
const visibleSteps =
  finalizeData == null
    ? getInitialSteps(draft)
    : getVisibleSteps(finalizeData.brand);
plaintext

设计点:finalize 前根据 draft flags 布局,finalize 后根据真实 brand 内容布局。这个切换让动画阶段不会展示最终一定为空的步骤,也不会在 brand 创建后突然消失。

和最近学习记录的关系#

和最近几天 agent runtime / image generation / deck eval 的主线没有直接模块关系;那些 PR 主要在 agent 协议、工具结果和用户确认边界上收敛。

但工程主题有相似性:#4309、#4389、#4580 都强调“跨层选择不能停留在 UI 状态”,需要进入 API/schema/runtime 契约;#4651 把同一原则用在品牌导入流程上,把“哪些步骤会有内容”变成后端 task result 的显式 contract。

我会怎么吸收#

  • 当 UI 的中间态依赖异步任务产物时,优先让任务结果返回窄 presence/summary 信号,而不是让前端猜或提前暴露完整内部 draft。
  • 阶段性数据源要明确:finalize 前用任务 draft projection,finalize 后用持久化实体;不要用一个阶段的数据假装覆盖所有阶段。
  • 跨语言仓库里,新增字段要从 schema 源头进入,生成物只是验证契约传播,不在生成文件里手写补洞。

边界/风险#

  • 明显风险不大,变更面小且 CI 中 IDL、Go、Frontend Quality、Frontend Unit Test、Frontend Browser Test 都成功。
  • 一个测试缺口是 PR 没有新增针对 getInitialSteps 或 BuildAPITask flags 的单元测试;如果后续 draft 结构变化,这组 presence 规则可能被无意破坏。
  • hasFonts 使用 len(draft.Texts) > 0 作为 fonts presence 的代理,语义依赖现有 brand import draft 结构;如果之后 texts 里混入非字体相关样本,需要重新审视字段命名或派生规则。

候选说明#

按 Australia/Melbourne 日期,2026-07-12 有 4 个已 merge 到 main 的候选:#4653、#4651、#4627、#4609。昨天窗口还有 12 个候选,其中 #4591 已在学习日志中记录。

最终选择 #4651,因为它用很小的 diff 解决了跨异步任务、API 契约和前端进度 UI 一致性的问题;相比今天几个更偏视觉还原或局部交互修复的 PR,它的工程设计信号更集中。

🗂️ 这是知识库中的🔬 研究。

内容可能仍在补充或修订中。

← Back