Skip to content

feat(fields): 采纳 file-as-reference 值形态 — ObjectStack ADR-0104 D3 wave 2 (PR-7) - #2828

Merged
os-zhuang merged 1 commit into
mainfrom
d3w2/file-reference-adoption
Jul 27, 2026
Merged

feat(fields): 采纳 file-as-reference 值形态 — ObjectStack ADR-0104 D3 wave 2 (PR-7)#2828
os-zhuang merged 1 commit into
mainfrom
d3w2/file-reference-adoption

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

配对 framework 侧的 ADR-0104 D3 wave 2(objectstack#3527 / #3534 / #3535,均已合并)。

三种形态,一处规则

file/image 字段值现在会以三种形态之一到达 UI。读取规则收敛到 @object-ui/fields 新增的 file-value 模块,不再由每个 widget 各自重新推导:

形态样子何时出现
引用sys_file id 字符串后端采纳 file-as-reference 后的存储形态
展开{ id, name, size, **mimeType**, url }读路径解析引用后返回的形态
遗留 blob{ file_id?, name, original_name, size, **mime_type**, url }本包此前自己构造的形态

这个 PR 修掉的真实 bug:大小写分裂

展开形态带的是 mimeType(camelCase,来自平台 spec 拥有的 FileValueSchema),遗留 blob 带的是 mime_type(snake_case)。而 FileFieldFileCellImageField只读 mime_type

后果:后端一旦开始返回展开形态,这三个 widget 立刻认不出图片——缩略图静默退化成通用文件图标,而症状里没有任何东西指向"值形态"这个原因。readFileValue() 两种都接受,所有 widget 都走它。

提交侧:上传后存引用

上传适配器给出 fileId 时,widget 提交裸 id(引用形态);没给出时(object-URL 兜底适配器、或早于 file-as-reference 的后端)提交遗留 blob。同一份构建对两种后端都工作

action param 本来就已经 POST 裸 fileId(#2698/#2710)——这个 PR 让记录字段值走上同一份契约,并且让 serializeParamValues 共用 fileIdOf() 提取器,两个面就不会在"什么算 id"上漂移。

UX:刚上传的文件立即可见

裸 id 本身不带 name 和 url,而丰富形态要等下一次读取才回来。所以每个 widget 会按 id 记住自己刚上传的文件的展示信息(withRecentUploads),上传完立刻正常渲染,而不是先显示一个裸 token 直到刷新。

测试

  • file-value.test.ts 新增 32 项:id token 的接受/拒绝矩阵(含 4 种 URL 形态)、fileIdOffile_id → id 优先级、展开形态与遗留 blob 两种大小写都识别成图片、name 回退链(name → original_name → URL 末段 → 本地化兜底)、裸引用诚实地不带 url、混合形态数组、提交两种模式并与 readFileValue 往返、meta.fileId 不是 id 形态时拒绝、withRecentUploads 只补裸引用且不改 raw(展示增强绝不改变提交内容)。
  • 全量回归:7418 passed / 0 failed(622 个测试文件) · type-check ✅ · build ✅ · lint exit 0

依赖与顺序

需要 framework 侧已合并的 PR-2(读路径解析 id)+ PR-3(写路径认领所有权)。因为提交侧带 fallback、读取侧三形态都认,这个 PR 可以先于 framework 的写切换(PR-5a)合并——合并后 PR-5a 就只是把已经在发生的事情正式收窄,而不是一次行为切换。

🤖 Generated with Claude Code

https://claude.ai/code/session_01SHpGw3GBA9aFpfwVArRWfd


Generated by Claude Code

…R-0104 D3 wave 2)
A file/image field value now reaches the UI in one of three forms, and the rules
for reading them live in one place — the new `file-value` module — instead of
being re-derived in each widget:
1. Reference a bare sys_file id string, what the backend stores once
file-as-reference is adopted
2. Expanded { id, name, size, mimeType, url }, what the read path returns
after resolving a reference
3. Legacy blob { file_id?, name, original_name, size, mime_type, url }, the
pre-reference shape this package used to build itself
The casing split is the bug this fixes. The expanded form carries `mimeType`;
the legacy blob carries `mime_type`. FileField, FileCell and ImageField all read
only `mime_type`, so the moment a backend starts returning the expanded form
they stop recognising images — thumbnails silently degrade to a generic file
icon, and nothing about the symptom points at a value shape as the cause.
readFileValue() accepts both, and every widget goes through it.
Uploads now submit the reference form — the bare id — when the upload adapter
surfaced one, and the legacy blob when it did not (the object-URL fallback
adapter, or a backend predating file-as-reference), so the same build works
against both. Action params already POSTed a bare fileId; record field values
now use the same contract, and serializeParamValues shares the fileIdOf()
extractor so the two surfaces cannot drift on what counts as an id.
Because a bare id carries no name or URL of its own, each widget remembers the
display details of files it just uploaded, keyed by id, so an upload renders
immediately instead of showing a bare token until the next read enriches it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SHpGw3GBA9aFpfwVArRWfd
@vercel

vercelBot commented Jul 27, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredJul 27, 2026 4:02am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)28.0 KB350 KB
Entry fileindex-D73A8kNq.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)8.19KB2.96KB
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)21.70KB4.21KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.12KB3.41KB
auth (LoginForm.js)17.86KB5.29KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.43KB2.09KB
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)33.74KB8.53KB
auth (createAuthenticatedFetch.js)4.37KB1.69KB
auth (index.js)1.83KB0.79KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.86KB0.85KB
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)450.70KB98.15KB
core (index.js)1.86KB0.63KB
create-plugin (index.js)9.28KB2.98KB
data-objectstack (index.js)127.29KB31.96KB
fields (index.js)214.60KB52.67KB
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)25.17KB5.80KB
i18n (useSafeTranslation.js)2.87KB1.28KB
layout (index.js)38.45KB10.67KB
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)4.42KB1.27KB
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)1.77KB0.77KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)6.84KB2.42KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.67KB1.12KB
permissions (evaluator.js)4.00KB1.23KB
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)45.37KB12.48KB
plugin-charts (index.js)46.90KB13.26KB
plugin-chatbot (index.js)179.53KB42.79KB
plugin-dashboard (index.js)108.71KB28.00KB
plugin-designer (index.js)210.92KB42.69KB
plugin-detail (index.js)214.78KB52.42KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)103.47KB25.10KB
plugin-gantt (index.js)162.33KB39.53KB
plugin-grid (index.js)176.51KB46.38KB
plugin-kanban (index.js)47.82KB13.18KB
plugin-list (index.js)98.71KB23.32KB
plugin-map (index.js)16.80KB5.24KB
plugin-markdown (index.js)13.65KB4.67KB
plugin-report (index.js)37.07KB9.81KB
plugin-timeline (index.js)25.37KB7.20KB
plugin-tree (index.js)8.36KB2.81KB
plugin-view (index.js)85.70KB20.87KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.55KB0.67KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)3.19KB1.38KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)18.70KB6.09KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.00KB0.55KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)2.16KB0.94KB
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)0.77KB0.41KB
types (disclosure.js)0.20KB0.18KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (index.js)1.97KB0.93KB
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.04KB1.93KB
types (system-fields.js)2.39KB1.17KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)0.75KB0.46KB
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

@os-zhuang
os-zhuang marked this pull request as ready for review July 27, 2026 04:08
@os-zhuang
os-zhuang merged commit 697cda4 into mainJul 27, 2026
14 checks passed
@os-zhuang
os-zhuang deleted the d3w2/file-reference-adoption branch July 27, 2026 04:08
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude