Appearance
Design JSON 真源约定与红线
状态:schema 已实现(验证原型级),代码在 packages/design-json/src/,公共入口 packages/design-json/src/index.ts 导出真实 schema(designSchema / layerNodeSchema / colorSchema / VersionHistory 等)。
一句话结构
Design JSON = Canvas JSON + Assets + Fonts + Dieline + Brand Tokens + 3D Mapping(ADR-0002)。 所有导出文件(PNG / SVG / 校稿 PDF / DXF)都是它的投影;投影可以重算,真源不能丢。
顶层实体见 packages/design-json/src/design.ts,逐字段清单见 字段表,可直接跑的样例见 最小可读完整示例。
三条写进 schema 的护栏
| 护栏 | schema 落点 | 违反时的行为 |
|---|---|---|
| 几何一律 mm | units.ts 的 mmSchema / positiveMmSchema;长度键统一 …Mm 后缀(canvasMm、sizeMm、safeAreaInsetMm、srcRectMm、dielineRectMm、thicknessMm) | 类型是裸 number,靠命名 + check.mjs 的键名检查守住;DEFAULT_PX_PER_MM = 4 只存在于渲染层 |
| 引用完整性 | design.ts 的 superRefine | 报 image 图层引用了不存在的 asset / text 图层引用了不存在的 font / 图层 panelId=… 在 3D mapping 中不存在 |
| 字体授权闸门 | fontRefSchema.licenseStatus + superRefine | 含文字层且字体为 blocked 时直接 parse 失败(外部依赖 PE-05 未清时保持 pending) |
layers 里的 id 必须全局唯一(同一份 design 内),superRefine 会报 图层 id 重复。
红线 1:禁止用整图重生成冒充编辑
"其他元素不变"必须是对象/图层/mask 级编辑,可审计、可回滚。落地抓手:
provenance.mode(design.ts的aiProvenanceSchema):枚举generate | inpaint | relayout | manual。 整图重生成会留下mode: "generate"的痕迹,评审时能一眼看出。provenance.affectedLayerIds:局部编辑必须声明受影响图层,这是"其他元素未变"的正面证据。aiEdits(顶层,最多 3 条):每次 AI 合成记录prompt / provider / maskDataUrl / resultDataUrl / sourceRef / costUsd / generationSeconds; 验证工作台的撤回操作就是把它pop掉(docs/demo/app.mjs)。VersionHistory(versioning.ts,P1-10已交付):rollback(targetVersionId)不是删除, 而是以目标快照追加新版本;不变量是回滚后 head 的内容与目标快照逐字段相等。- 版本种类与确认指针(
P1-10):每个版本带kind: manual | ai | confirm | rollback,kind:'ai'还带source{provider,model,jobId,prompt,costUsdCents,affectedLayerIds,mode}(金额未知记null,不编造)。 确认指针只被confirm()/ 人工append()/rollback*()推进,appendAi()永不推进—— 所以hasUnconfirmedAi()为真时discardPending()只会丢 AI 产物,删不掉用户自己的编辑。 - 整图重生成闸门(
P1-10):appendAi(design, { mode: 'generate' })若会减少既有图层直接抛错, 必须显式allowLayerLoss: true才放行;mode:'inpaint'(局部)不受此限。这条把红线 1 从口头约定变成运行时异常。 - 快照隔离与崩溃恢复(
P1-10):快照structuredClone+ 深冻结(外部改head().design抛TypeError), 要可写副本用headDesign();toJSON()/fromJSON()往返保内容与指针,载入时重跑 schema 与链校验, 缺父版本 / 重复 id / 时间倒序 / 跨设计 / 指针悬空一律抛错。 - 像素级独立复验:
tools/check-proof-evidence.py断言outside_mask_changed_pixels === 0且选区内变化 > 10000 px, 即"遮罩外一个像素都不许动"。这是机器证据,不是口头承诺。
未实现:服务端版本事务与 delta 存储(deltaFromParent 仍是占位字段,v0.1 存全量快照)属 P2-13; 版本树分叉与并合(多人协作、AI 分支比较)也属 P2-13——当前是线性链,from() 遇到分叉直接抛错。
红线 3:预览 RGB 与生产色分离
colorSchema 是 mode 判别的联合:rgb(0–255)/ cmyk(0–100 百分比)/ spot(spotName + tint + 可选回退)。
- 画布预览用
previewRgbOf()(color.ts)。CMYK 分支是教科书式朴素换算,源码注释明确"禁止把它当印刷色"。 - 专色没有
rgbPreview也没有cmykFallback时,预览故意显示成品红占位,逼我们补语义。 - 渲染层同样朴素:
docs/demo/scene.mjs的cssColor把 spot 一律画成固定的#b43eaa。 - 未实现:按目标 ICC 真正落色属
P3-08;PDF/X-4+Processing Steps 导出属P3-09(当前状态blocked,依赖采购决策PE-03)。 也就是说:今天导出的校稿 PDF 不是 PDF/X,也不声称生产落色已验收。
红线 4:AI 图内文字不是最终印刷文本
TextLayer.source / BarcodeLayer.source 取值 manual | aiRebuilt | aiReference:
aiReference= 只读参考素材,不得作为生产值。BarcodeLayer更硬:source === "aiReference"时 schema 直接报错条码值不能由 AI 图像识别提供,必须来自人工录入或 GS1 数据源,其余来源还要过校验位/字符集(barcodes.ts)。- 细则与工艺术语见 文字/条码重建与局部重绘硬规则。
当前投影能力(导出)
| 投影 | 状态 | 位置 | 边界 |
|---|---|---|---|
| Design JSON 自包含保存 | 已实现(验证原型) | docs/demo/app.mjs、IndexedDB | 图片必须内嵌 dataUrl,禁止回落到本地路径 |
| SVG | 已实现(验证原型) | exportSceneSvg(docs/demo/scene.mjs) | 页面 width/height 为 mm,图片内嵌,结构线带 data-aux;可编辑文字仍依赖同名字体 |
| 校稿 PDF | 已实现(验证原型) | tools/render-document.mjs + tools/fix-pdf-page.py | 由同一份 SVG 经 Chromium 输出,中文子集嵌入,页面 mm 精确到 mediabox;不是 PDF/X |
| DXF | 已实现(验证原型) | exportDxf(docs/demo/scene.mjs) | AC1015、$INSUNITS=4(毫米)、DIE_CUT/CREASE/GLUE 分图层;CAD 兼容性待接收印厂确认 |
| PDF/X-4 + 加工层 | 未实现 P3-09(blocked,依赖 PE-03) | 规划 workers/export/src/pdffx.ts | 不得对外称生产文件 |
| PNG 位图 | 已实现(演示用途) | docs/demo | 只作预览/参考,不是交付物 |
改 schema 的维护规则
- 先改 zod(
packages/design-json/src/),跑pnpm --config.verify-deps-before-run=false test packages/design-json。 - 同一 PR 内同步本站点 字段表 与 示例,再跑
node docs-site/check.mjs。 - 版本迁移:ADR-0002 要求
meta.schemaVersion+ 迁移器;当前实现是顶层字面量字段schemaVersion(SCHEMA_VERSION = 1), 迁移器未实现,属P1-10/P2-13范围。这一偏差记录在 ADR 摘要与偏差。 json-schema.ts能导出 draft 2020-12 的 JSON Schema(给 Java/印厂/站点用),但不含superRefine的自定义规则; 真校验永远走 zod。