# 表单设计器 toJSONSchema 完整性 gap

## 现象

`frontend/src/features/forms/components/designer/types.ts` 的 `FieldType` 联合类型枚举了 30+ 字段类型，但 `toJSONSchema()` 函数的 switch 只覆盖一部分。漏掉的字段（rating / serialNumber / address / region / cascade / signature / richtext）在序列化时落入默认分支，**`fieldSchema.type` 始终为 undefined**。前端 `FormFieldRenderer` 拿到 `type: undefined` 直接走 fallback 显示"不支持的字段类型: undefined"。

## 为什么这是 trap

- TS 联合类型本身**不会**强制 switch 覆盖所有 case（除非用 `never` 兜底做 exhaustiveness check）
- 设计器里这些字段**有图标、能拖拽、能配置**，看起来像是"已实现"，但发布表单时静默吐出 schema 缺 `type`
- 后端 JSONB 不校验 schema，submit 也照样落库（因为 form_instance.data 是 freeform）
- 唯一暴露处是 renderer 的 fallback 字符串

## 修复模式（参考本次 PR）

实现一个新字段类型必须**三层都改**：

1. **`toJSONSchema()` switch（types.ts ~815 行）**：emit `type` + 必要的 `format`/`x-*` 元数据
2. **`widgetMap`（types.ts ~1049 行）**：把 FieldType 映射到 `ui:widget` 字符串，给 renderer 识别
3. **`parseFieldFromSchema()`（types.ts ~1242 行）**：反向识别 widget→FieldType，否则保存→重新打开设计器会丢字段
4. **`FormFieldRenderer.tsx`**：根据 `type` + `widget` 加渲染分支

漏 (3) 的症状：设计器保存后再打开，原本的 rating 字段变成 number、signature 变成 textarea。

## 防御建议

在 `toJSONSchema()` 的 default 分支加 exhaustiveness check：

```ts
default:
  // 触发 TS error 当未覆盖的 FieldType 加入
  const _exhaustive: never = field.type as never;
  console.warn(`[toJSONSchema] unhandled field type: ${field.type}`);
```

或者维护一张 `FieldType → schema generator` 的映射表，TS `Record<FieldType, ...>` 强制覆盖。

## 触发场景

- 新增字段类型时
- 重构 `toJSONSchema` 时
- "为什么 renderer 显示不支持的字段类型？" 排查时（先看 schema 输出，多半是序列化层缺 case）
