Repository navigation
字段多的对象:自适应呈现表面 + 语义 field span(auto/full)+ 表单布局防呆 lint(AI-authored,ADR-0085 对齐) #2578
Description
Activity
- addedenhancementNew feature or requestNew feature or request
on Jul 4, 2026 设计细化(讨论后 · 2026-07-05)
讨论收敛出一条更硬的原则 + 几个具体改动。
原则:表现全部运行时派生,AI 零像素授权
AI 只写数据事实(字段 +
fieldGroups/highlightFields等语义角色)。surface / 宽度 / 列数一律运行时算——它们要么机器能推(字段数),要么只有客户端知道(视口)。AI 不知道客户端浏览器宽度,所以任何像素/宽度都不该进元数据。AI 写"数据长什么样",客户端写"这块屏幕这会儿怎么摆"。像素永远不进元数据。
两个正交轴,都默认
auto轴 取值 说明 mode(在哪开) auto/ drawer / modal / pageauto:字段数 ≥12→page、<12→drawer;mobile→page size(多大,仅浮层) auto/ sm / md / lg / xl / fullpage 是整页路由,无 size spec 清理:像素宽度作废/弃用,统一档位
- 弃用:
NavigationConfig.width(string | number 像素)、FormView.drawerWidth(像素)。渲染器保留读取作后备,但标记 deprecated、AI 不写。 - 统一:一套档位枚举,和已有的
modalSize: sm/default/lg/xl/full对齐(加auto默认、default→md)。抽屉 / 弹窗 / 详情导航共用一套词汇。 auto(默认,AI 零书写)= 运行时按字段数选档 +min(档位, 92vw)夹住视口。- 显式档 = AI 安全的粗覆盖(意图非像素、与视口无关;
modalSize已证明粗档位既有用又不会写错)。
列数:容器查询按实际宽度定,上限随字段数放大
- 已是容器查询驱动(
@container+@md/@2xl/@4xl:grid-cols-N),看容器实际宽度、不看视口。 - 缺口:
inferColumns上限卡在 2。改为按字段数放大上限(→ 3 / 4),容器查询负责当前宽度实际显示几列。 - 效果:同一表单,窄抽屉 1 列 → 宽抽屉 2-3 列 → 整页 3-4 列,拉宽缩窄自动跟。
本轮开发
- objectui:
inferColumns上限按字段数放大(≤3→1 / ≤8→2 / ≤15→3 / ≥16→4);列数随容器实际宽度响应。 - spec:
NavigationConfig.size档位枚举(auto默认)+ 弃用像素width/drawerWidth。 - objectui:消费
size+ 运行时按字段数派生宽度上限,min(档位, 92vw)。 - 浏览器验证:强制 drawer 的 field_zoo 拉宽/缩窄 → 列数跟着变。
仍待定(下一轮设计,先不做)
- 新增 / 编辑的表面与返回:详情=路由已定;新增/编辑走全屏弹窗还是路由未定(我倾向全屏弹窗:关掉即返回、无返回栈)。
- 子表新增 / 编辑:建议永远是父详情页上的弹窗(关闭即回父页 + 子表刷新),不切路由。
- 弃用:
- added 5 commits that reference this issue
on Jul 4, 2026 ✅ Shipped & merged (all green)
- framework#2595 — spec
deriveRecordSurface+FormField.span+NavigationConfig.size(deprecates pixelwidth/drawerWidth) +validateFormLayoutlint + ADR-0085 §2 clarification. - objectui#2237 — adaptive surface (page vs drawer, from field count) + per-section
columnsfix (console(11.5.0): 录入表单 type:'simple' 未按 spec 声明的 section.columns 渲染多列(分组内字段始终单列) #2515) +spanconsumption + responsive column cap + runtime overlay width. - framework#2599 —
objectstack-uiauthoring skill: new "Record Presentation — surface / width / columns are auto-derived" section.
Delivered effect (browser-verified): field-heavy records open full-page, light ones as a drawer — both derived from field count, zero AI-authored presentation. Forms render multi-column following the real rendered width (1→2→3→4 via container queries). Verified end-to-end:
field_zoo(57 fields) → full-page detail + 4-col form at 1680px / 2-col at 760px / 1-col at 430px;product(6 fields) → drawer.Deferred to a follow-up
The create/edit/subtable return-flow model (full-screen modal vs route; subtable-create as a modal over the parent detail) was discussed but intentionally not implemented here — adjacent to this issue's scope. Recommend a dedicated issue.
Closing — core scope delivered.
- framework#2595 — spec
Return-flow follow-up filed: #2604 (record create/edit/subtable surface + return model).
- added a commit that references this issue
on Oct 7, 2026
✅ 开发效果(Delivered)
一句话
字段多的对象,新增 / 编辑 / 查看详情自动切换到整页窗口;字段少的对象保持抽屉 / 弹窗。全部由平台按字段数自动推断,AI 写元数据时零额外配置——不暴露
recordSurface这类可写 key,所以 AI 无从写错。Before → After
section.columns)type:'simple'忽略 per-section 列数,分组内字段始终单列(#2515)colSpan(1–4),换表面(手机 / 弹窗 / 整页)就错位span(auto/full),与派生列数解耦,任何列数都不溢出;遗留colSpan渲染期 clamp,永不越界form-field-unknown/absolute-colspan-discouraged),AI 与人手写同一把尺(ADR-0019 / 0078)recordSurface开关 + 新 ADR浏览器验证(真 console + app-showcase 后端,端到端跑通)
field_zoo/…/showcase_field_zoo/record/<id>,面包屑 + 两列详情,无遮罩product?recordId=<id>,右侧抽屉浮层覆盖虚化的列表录入表单同时验证多列:
Email │ URL两列、Phone │ Password两列、Textarea独占整行(宽控件span:auto自动整行)。用户 / AI 视角
交付与验证
f79263a19:deriveRecordSurface(spec/src/data/record-surface.ts)+FormFieldSchema.span+packages/lint/src/validate-form-layout.ts+ ADR-0085 §2 澄清 + changeset。spec 6677 测试、api-surface(regen)、lint 121 全绿。97726afa4+a56cb420f:autoLayout.resolveColSpan(span 感知 / clamp / per-section 密度)+ObjectFormgrouped 路径修 console(11.5.0): 录入表单 type:'simple' 未按 spec 声明的 section.columns 渲染多列(分组内字段始终单列) #2515 +FormField.span透传 + app-shell 记录表面接入deriveRecordSurface。autoLayout 43 + plugin-form 173 测试、turbo type-check 29/29 全绿。packages/app-shell/src/views/ObjectView.tsx决定(原默认drawer),已在其detailNavigation默认接入deriveRecordSurface——底层 plugin-view 改动不够,必须在此层接入才在 console 生效。已知边界 / 后续
section.columns」路径:现有 showcase 对象都不声明 per-section 列数,该分支由单测覆盖(per-section 密度 4 个用例),无浏览器截图。deriveRecordSurface目前是本地镜像(objectui pin@objectstack/spec@^11.7,早于该导出);待 spec ≥ 11.10 发版后换成@objectstack/spec/data的 import(代码已留 TODO 注释,逻辑与阈值与 spec 一致)。背景
字段特别多的对象(50+ 字段),来自 #2574 的两个诉求:
北极星约束:所有元数据都是 AI 写的,所以设计的核心目标不是"能配",而是让 AI 无从写错。下面每个决策都围绕这一点。
关键决策(为什么不走最直觉的做法)
1. 不新增
recordSurface之类的对象级 key按 ADR-0085 §2 的"未来 key 准入测试",呈现表面(modal/page)两条都不过:
→ 呈现表面 = 渲染器启发式(auto) + assigned page 覆盖,不是 authored key。
2. 不新写 ADR / 设计文档
方案完全落在 ADR-0085 已画的边界内(auto 派生 + assigned page),理由进 changeset + 代码注释即可(合"fewer ADRs"取向)。唯一可选动作:往 ADR-0085 §2 的 rejected-keys 清单补一行 "modal-vs-page presentation surface" 例子,防止有人(讨论中我本人就提过又撤回)再提
recordSurface。3. 绝对
colSpan在自动适应下 fragile,改语义span列数是派生的(手机 1 / modal 2 / 整页 3–4),绝对
colSpan: 3只在脑补的那个宽度下对,换表面就溢出/错位。→ 字段级排版原语改为与列数解耦的
span: 'auto' | 'full'(默认auto)。auto:渲染器按字段类型 × 派生列数自动排(标量按密度并排,textarea/richtext/子表等宽控件自动整行)——90% 情况 AI 一个都不用写。full:任何列数下独占整行。half:本期不做(三者里 AI-safety 最弱:奇数列要取整、很多成对关系渲染器能自己推、多一个可选错的旋钮)。等 dogfood 真的暴露"成对字段在宽页被拆散"再议。4. 多列的地基已经有了
ADR-0085 的
fieldGroups+Field.group+collapse、FormSection.columns(1–4)、RecordDetails.columns都已在 spec,并已被validate-semantic-roles.ts守住字段引用。缺口:表单视图内sections[].fields引用 +colSpan没有对应交叉校验;renderer 侧 #2515(section.columns没渲染成多列)。贯穿全案的防呆原则(设计脊椎)
闭合枚举(编译期拒) > 智能默认(让 AI 不用写) > 单一派生器(不漂移) > 两级 lint(warning + hint) > schema.describe 即提示词。
依据:ADR-0078(no-silently-inert)、ADR-0019(lint 对 AI 和人手写同一把尺)、ADR-0085(object-level 语义角色 / 准入测试)。
开发计划
Step 1 — framework(一个 PR;纯函数 + 文档;可单测;无需浏览器;独立可合)
deriveRecordSurface(fieldCount, groups)—— 放packages/spec/src/data/,与deriveFieldGroupLayout并列。从字段数/分组单源派生 page/modal/drawer(移动端强制 page)。是派生,不是 authored key(ADR-0085 §5「one shared derivation, every surface」)。单测覆盖阈值边界、有/无 fieldGroups、mobile。FormFieldSchema.span: z.enum(['auto','full']).default('auto')—— 加在packages/spec/src/ui/view.zod.ts。保留colSpan兼容。.describe()写成 AI 引导语:"默认省略(按类型自动);整行用full;别用绝对列数,表面列数是派生的。"packages/lint/src/validate-form-layout.ts(照validate-semantic-roles.ts模子):form-field-unknown(warning):section 引用了对象上不存在的字段;absolute-colspan-discouraged(warning + hint):用了colSpan,引导改span:'full'(自动列数下 overflow 无良定义,故不做比大小)。导出进 lint index。
验收:单测绿;
os validate对种子坏对象吐出两条新 warning;spec 为 additive(不升 major)。Step 2 — objectui + 浏览器 dogfood(一个 PR)
ObjectView/ObjectForm消费deriveRecordSurface();create/edit/detail 统一走派生表面,替掉硬编码的layout='drawer'默认。span:'auto'→ 宽度按widget 类型 × 派生列数(宽控件自动整行);span:'full'→ 整行;遗留colSpan渲染期 clamp =min(colSpan, 当前列数)(降级不破版,ADR-0078)。section.columns未渲染成多列)作为本 PR 一部分。Non-goals / 已否决的替代方案(避免重复讨论)
recordSurface(或任何 object 级 modal/page 开关)—— 被 ADR-0085 §2 准入测试否决。span:'half'—— 暂缓,等 dogfood 暴露真实成对断裂再议。colSpan作为主要排版原语 —— 仅保留兼容,lint 引导迁移。Refs