Skip to content

fix(plugin-form,types): carry mobile_fullscreen on the field metadata, where widgets actually read (#3245) - #3300

Merged
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-3245-fullscreen-flag-carrier
Aug 3, 2026
Merged

fix(plugin-form,types): carry mobile_fullscreen on the field metadata, where widgets actually read (#3245)#3300
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-3245-fullscreen-flag-carrier

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Closes#3245

按 2026-08-03 维护者裁决走 方案 A:生产者把 flag 盖到元数据载体上;mobile_fullscreen不进@objectstack/spec,只在 @object-ui/types 的 widget 侧字段元数据类型上声明。方案 B(新增宿主 prop 契约)未采纳。

前置核对

基线含 #3233(PR #3296packages/fields/src/withFieldCarrier.tsx 已在 origin/main)。issue 正文的六步断链在现状下逐条复核仍然成立(行号已漂移,以下为现状行号):

步骤现状位置事实
1ObjectForm.tsx:567自动生成字段 type: mapFieldTypeToFormType(field.type)field:textarea
2ObjectForm.tsx:584同时 field: field 把原始元数据挂到 .field
3ObjectForm.tsx:1049mobile 步骤 ({ ...f, mobile_fullscreen: true } as FormField) —— 盖在 FormField 自己身上
4form.tsx:1826-1837field:textarea 不在 BUILTIN_FIELD_TYPES,走已注册 widget 分支
5form.tsx:1372转发 `field: field.field
6form.tsx:275-289stripRegisteredFieldProps 显式剔除 mobile_fullscreen prop

结论未变:自动生成字段拿不到 flag,手写 customFields 拿得到——而自动生成是绝大多数表单的路径。

断链证明(先红后绿)

按裁决指定的顺序,先补集成用例钉住现状。用例全链路真实:真 ObjectForm → 真 form 渲染器 → 真注册表 → 真 TextAreaField,除 dataSource 外无 mock。这个特性此前零集成覆盖,两端的单测各自全绿,正是它断了这么久没人发现的原因。

改生产者之前(红):

 × ... > reaches the widget for an AUTO-GENERATED long-text field 1043ms
✓ ... > still reaches the widget for a HAND-AUTHORED customFields long-text field
✓ ... > does NOT render the affordance when the form did not opt in
Tests 1 failed | 2 passed (3)

改生产者之后(绿):

 ✓ ... > reaches the widget for an AUTO-GENERATED long-text field 79ms
✓ ... > still reaches the widget for a HAND-AUTHORED customFields long-text field, without displacing its other metadata 14ms
✓ ... > does NOT render the affordance when the form did not opt in 28ms
Tests 3 passed (3)

改动

生产者(packages/plugin-form/src/ObjectForm.tsx —— flag 盖到「渲染器将要转发为 field 的那个对象」上,即用渲染器自己的解析方式 f.field || f 解析载体:

if(!isTextarea)returnf;returnf.field
? { ...f,field: { ...f.field,mobile_fullscreen: true}}
: { ...f,mobile_fullscreen: true};

as FormField 硬转随之消失(FormField.field 本就声明为 Record< string, any >,无需强转)。

类型(packages/types/src/field-types.ts —— mobile_fullscreen?: boolean 声明在 BaseFieldMetadata 上,因而落在 FieldWidgetComponentProps.field 所解析到的整个 FieldMetadata 联合上。注释写明:它是表单级 ObjectFormSchema.mobile.fullscreenLongText 向字段元数据的投影,生产者唯一(ObjectForm),消费者唯一(TextAreaFieldfield#3233 之后没有第二个载体),不是可书写元数据、不进 @objectstack/spec

声明在 base 而非单个 TextareaFieldMetadata 成员上,是因为 TextAreaField 拿到的是未收窄的联合——声明在单个成员上会让那次读取变成编译错误,把下一个作者直接推回 as any,也就是这个 issue 要消灭的东西。

为什么保留「无 .field 时盖在 FormField 上」这一支

不是防御性冗余,而是被测试逼出来的。无条件写成 { ...f, field: { ...f.field, flag } } 时,手写 customFields 的对照用例照样绿——因为它从 undefined 凭空造出一个只含 flag 的元数据对象,flag 确实到达了 widget。但渲染器随后会把这个 stub 当作 field 转发,而 TextAreaFieldrows / placeholder从元数据读的、不是从 props 读的,于是它们静默退回默认值。

所以对照用例被加强为同时断言 rows="9"placeholder,这条捷径才会失败而不是通过。

Sabotage 验证(每条新断言逐一验证)

#破坏结果
S1还原为改前写法(只盖 FormField)仅「AUTO-GENERATED」红 ✅
S2去掉无 .field 分支(无条件盖元数据)仅「HAND-AUTHORED customFields」红 ✅
S3忽略 mobile.fullscreenLongText 开关仅「did NOT render」红 ✅
S4删掉 BaseFieldMetadata 上的声明tsc -p tsconfig.test.json 报 6 处 TS2339/TS2353 ✅

S2 是加强对照用例前没能红的那一条——先失败、再据此补强断言。

验证

pnpm exec vitest run packages/plugin-form packages/types packages/fields --maxWorkers=2
Test Files 101 passed (101)
Tests 1225 passed (1225)
turbo run type-check --filter @object-ui/types --filter @object-ui/plugin-form \
--filter @object-ui/fields --filter @object-ui/components
Tasks: 15 successful, 15 total
turbo run lint --filter @object-ui/types --filter @object-ui/plugin-form
0 errors(类型断言的 unused-var warning 与既有 page-node-type-contract.test.ts 完全同形)
node scripts/check-changeset-no-major.mjs
✅ No changeset declares a `major` bump.

changeset:minor(additive 类型声明 + 行为修复),正文写明「flag 的唯一合法载体是字段元数据,生产者是 ObjectForm」。

范围

只改 packages/plugin-form + packages/types + 测试。未触碰packages/components / packages/fields 源码(#3233 收敛态是只读依赖,#3290 正在并行改 form.tsx),spec 零改动,content/docs/releases/ 未碰。

顺带发现的相邻缺陷已另开 unassigned issue,不在本 PR 修。


Generated by Claude Code

…ta, where widgets actually read (#3245)
`ObjectForm` stamped the fullscreen-textarea opt-in onto the FormField
itself. The form renderer forwards `field: field.field || field`, so an
auto-generated field — which always stashes the object-field metadata on
`.field` — handed the widget metadata that never carried the flag, while
`stripRegisteredFieldProps` dropped the FormField-level copy. Every
generated form silently lost the feature; only the hand-authored
`customFields` path (no `.field` to shadow the FormField) ever worked.
Stamp the flag onto the object the renderer will forward — `f.field || f`,
resolved exactly as `renderFieldComponent` resolves it — so there stays one
carrier in one place (#3232 / #3233). The FormField branch is kept for the
no-`.field` case: synthesizing a metadata object there would light the
affordance up while replacing the field's `rows` / `placeholder` with
defaults.
Declare `mobile_fullscreen` on `@object-ui/types`' `BaseFieldMetadata`,
hence on the `FieldMetadata` union `FieldWidgetComponentProps.field`
resolves to. Not an `@objectstack/spec` property — it is a projection of
the form-level `ObjectFormSchema.mobile.fullscreenLongText` setting, not
authored metadata. This removes the producer's `as FormField` cast, the
last untyped end of the chain.
Adds the feature's first integration coverage: real `ObjectForm` → real
form renderer → real `TextAreaField`, no mocks. It fails against the old
producer and passes against the new one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVPjPzmmAJ2Ngtvgg5MSRa
@vercel

vercelBot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredAug 3, 2026 1:19pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)28.1 KB350 KB
Entry fileindex-5Pv_tAnY.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)8.47KB3.09KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)7.57KB2.97KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)22.10KB4.37KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)35.76KB9.11KB
auth (createAuthenticatedFetch.js)4.37KB1.69KB
auth (index.js)2.35KB1.07KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.91KB0.87KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)18.38KB4.49KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)3.65KB1.42KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.25KB0.53KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)476.66KB104.69KB
core (index.js)2.25KB0.80KB
create-plugin (index.js)9.28KB2.98KB
data-objectstack (index.js)136.23KB34.75KB
fields (index.js)223.38KB54.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.46KB0.96KB
i18n (pickLocalized.js)1.70KB0.83KB
i18n (provider.js)5.37KB1.72KB
i18n (useObjectLabel.js)26.14KB6.07KB
i18n (useSafeTranslation.js)3.26KB1.44KB
layout (index.js)37.96KB10.54KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.05KB1.53KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)8.75KB3.06KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.67KB1.12KB
permissions (evaluator.js)4.41KB1.44KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.55KB0.71KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)44.98KB12.37KB
plugin-charts (index.js)60.54KB17.13KB
plugin-chatbot (index.js)180.09KB42.72KB
plugin-dashboard (index.js)112.01KB28.86KB
plugin-designer (index.js)210.51KB42.51KB
plugin-detail (index.js)230.56KB56.80KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)111.57KB26.98KB
plugin-gantt (index.js)162.25KB39.55KB
plugin-grid (index.js)185.08KB49.04KB
plugin-kanban (index.js)47.89KB13.18KB
plugin-list (index.js)104.94KB25.32KB
plugin-map (index.js)16.81KB5.24KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)40.55KB10.59KB
plugin-timeline (index.js)25.76KB7.33KB
plugin-tree (index.js)8.34KB2.82KB
plugin-view (index.js)83.67KB20.43KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.67KB2.37KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)19.28KB6.38KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.02KB0.55KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)4.47KB2.03KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)2.46KB1.21KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)0.20KB0.18KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@xuyushun441-sys
xuyushun441-sys marked this pull request as ready for review August 3, 2026 13:38
@xuyushun441-sys
xuyushun441-sys added this pull request to the merge queueAug 3, 2026
Merged via the queue into main with commit f44d872Aug 3, 2026
17 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the claude/issue-3245-fullscreen-flag-carrier branch August 3, 2026 13:38
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Aug 6, 2026
…een 别名 (objectstack-ai#3303) (objectstack-ai#3397)
* fix(components): read the fullscreen long-text flag on one spelling (objectstack-ai#3303)
The built-in `textarea` branch resolved the flag as
`mobile_fullscreen || fullscreen`, and both prop strips carried a matching
entry discarding a `fullscreen` key. That alias had zero producers: neither
this repo nor `@objectstack/spec` publishes a form-field `fullscreen`
property (the only `fullscreen` keys that exist belong to the unrelated
feedback/loading overlay), so the second term was undefined from the day it
was written.
Its cost was not a wrong value, it was a second spelling: the renderer
advertised a flag that quietly does nothing, which is the lenient consumer
fallback AGENTS.md #0.1 forbids and the same mechanism as objectstack-ai#3245 / objectstack-ai#3301.
`ObjectForm` is the sole producer and stamps `mobile_fullscreen`
(objectstack-ai#3245/objectstack-ai#3300) — also the single spelling `TextAreaField` and `RichTextField`
read, so the built-in branch was the last place where a producer-less
spelling still "worked".
Removing the strip entries puts `fullscreen` in the ordinary unknown-key
class rather than giving a key nobody produces a dedicated discard.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
* docs(changeset): record the fullscreen-flag convergence (objectstack-ai#3303)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
---------
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ObjectForm 的 mobile.fullscreenLongText 到不了自动生成字段的 TextAreaField:flag 写在 FormField 上,form.tsx 转发的却是 field.field

2 participants

@xuyushun441-sys@claude