Skip to content

fix(detail): a credential field is never inline-editable on the record page - #4228

Merged
yinlianghui merged 2 commits into
mainfrom
claude/issue-4221-inline-credential-gate
Aug 11, 2026
Merged

yinlianghui merged 2 commits into
mainfrom
claude/issue-4221-inline-credential-gate

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #4221

A password or secret field on the record detail page is no longer inline-editable: no pencil, no double-click affordance, no editor — on both hosts (the DetailSection body row and the HeaderHighlight strip).

The defect, as measured

InlineFieldInput has no branch for either type, so both reached the terminal raw text input at the end of the component. The hosts' editability gate excluded only readonly / computed / system fields, so nothing held a credential row back.

Both types are masked on read — getCellRenderer returns a fixed bullet run for password and secret alike — so the value the row could hand an editor was never the credential. It was whatever the payload carries: a server-side mask, or, for secret, an opaque reference into an encrypted store (ADR-0100). That placeholder was seeded into a plain input[type="text"], rendered in clear, selectable and copyable, in a control the user reads as holding their credential; committing the row wrote it back verbatim over the field.

Reverting only the two host gates on this branch reproduces it exactly (27 tests red), and the assertion output is the defect itself:

FAIL  DetailSection — a credential field offers no inline editor (#4221)
      produces no editor for a `password` even with the section in edit mode
AssertionError: expected  input …(3) /input  to be null
+ Received:
  type="text"
  value="••••••"

FAIL  HeaderHighlight — a credential highlight offers no inline editor (#4221)
      produces no editor for a `secret` even inside the edit session
+ Received:
  type="text"
  value="sec_ref_9f2a41"

One refinement to the issue's wording, since it is load-bearing for how bad this is: the editor is not seeded from the mask renderer — it is seeded from the row's payload value. The mask renderer ignores its value entirely. So the box shows whatever the API actually sent for that field, and the write-back destroys it. For secret that is the ADR-0100 reference, i.e. the pointer into the encrypted store, not merely a displayed value.

The fix

The decision was already written down one package over. INLINE_EXCLUDED_FIELD_TYPES in @object-ui/fields excludes both types with exactly this reasoning, and the grid honours it through isInlineExcludedFieldType() (plugin-grid/src/inline-edit-options.ts). Both detail hosts now consult that same alias-aware contract via a new isInlineExcludedDetailFieldType() in fieldEnrichment.ts — the module the two gates already share — instead of growing a second hand-maintained list.

No export had to be added: packages/fields/src/index.tsx already does export * from './FieldEditWidget', which is how plugin-grid imports the helper today. packages/fields is untouched, so the changeset is @object-ui/plugin-detail alone.

The helper is a narrow-only union of the authored view type and the object type, matching isComputedFieldType under objectui#3355: an authored display type can lock a field but never unlock one. An image authored over an object secret stays locked, because the exemption below is consulted per type, before the union.

Blast radius of the consultation

Every member of INLINE_EXCLUDED_FIELD_TYPES, plus the spec spellings that resolve into it through the form alias table. "Before" is measured on this branch with the gates reverted, not inferred.

Type Resolves to Detail before Detail after Why
password itself editable excluded masked on read; the card
secret itself editable excluded masked on read + ADR-0100 ref; the card
object itself editable excluded object-shaped value into a text box (#4220 container half)
composite object editable excluded same, alias
record object editable excluded same, alias
grid itself editable excluded same
repeater grid editable excluded same, alias
vector itself editable excluded embedding vector into a text box
markdown itself editable excluded heavy editor; a one-line text box is lossy
html itself editable excluded same
richtext itself editable excluded same
object-ref itself editable excluded authored in the record form (depends on a loaded catalog)
filter-condition itself editable excluded same
recipient-picker itself editable excluded same
autonumber auto_number editable excluded the detail computed gate only knows the auto_number spelling
auto_number itself already locked unchanged detail computed gate
formula itself already locked unchanged detail computed gate
summary itself already locked unchanged detail computed gate
file itself editable kept (exempt) InlineFieldInput routes FileField
video file editable kept (exempt) same, alias
audio file editable kept (exempt) same, alias
image itself editable kept (exempt) InlineFieldInput routes ImageField
avatar itself editable kept (exempt) InlineFieldInput routes AvatarField
signature itself editable kept (exempt) InlineFieldInput routes SignatureField

Why the binary family is exempt rather than gated

This is the one place a blind consultation would have removed a working editor, so it is gated selectively and the exemption is justified from the shared set's own text. The set carries several different arguments under one name, and only some are about the value:

  • The binary/attachment entries are excluded there for a grid-cell reason — the set's own comment reads "edited from the record form, shown read-only in the grid" — because a cell cannot host an upload dropzone. A detail row can, and does: InlineFieldInput routes image / avatar / signature / file (and the video / audio spellings) to the very widgets the record form uses, added deliberately so inline edit could preview, replace and remove files instead of showing a bare storage URL. Gating them would be an unrelated feature regression riding a credential-safety fix.
  • The credential and container entries are excluded for a value reason — masked on read, or object-shaped. That argument transfers to the detail page verbatim, because the detail fallback is the same plain text input the grid's is.

The exemption is not a second free-floating list: inlineCredentialGate.test.tsx pins every entry twice — it must be a real member of the shared set, and InlineFieldInput must really render something other than the terminal text input for it. An exemption that outlives its routing fails the suite instead of silently re-opening the plain-text path.

Overlap with the neighbouring cards (reported, not expanded)

Verification

  • pnpm exec vitest run packages/plugin-detail/src/__tests__/inlineCredentialGate.test.tsx — 75 passed. Red-first: the same file was 27 failed / 48 passed before the hosts were wired.
  • Reverse verification: git checkout origin/main -- DetailSection.tsx HeaderHighlight.tsx → predicted RED, got 27 red with the excerpt above; restored from the commit → 75 green.
  • pnpm exec vitest run packages/plugin-detail/ packages/fields/ — 1805 passed, 143 files. One pre-existing failure, recordDetailsInputs.spec-parity.test.ts, which reproduces with this branch's source files reverted to origin/main and is unrelated to this change: its fixture authors layout: 'custom', a record:details property @objectstack/spec 17.0.0-rc.6 removed (objectstack#6946, ADR-0087 D2), so safeParse now rejects it with expected: "never". main is red on it as of 6314e87f2. Reported on record:details 的 layout 发布了 auto|custom 语义,渲染器唯一的读点只认 spec 已退役的 inline|compact —— auto/custom 从未被实装 #3818, whose subject is that same key.
  • pnpm --filter @object-ui/plugin-detail type-check — clean. eslint on the touched files — 0 errors.
  • pnpm check:control-bytes — OK (3924 tracked files); plus a targeted self-scan of the touched files, no hits.
  • No new user-visible strings, so no i18n work: the change only withholds affordances that already existed, and the one string involved (detail.editInlineHint) is untouched.

Serialization: branched after #4222 (fix(detail): inline-edit an address as sub-fields) and rebased onto it — the two changes touch disjoint files (#4222 is InlineFieldInput.tsx + its own test + changeset; this is fieldEnrichment.ts / DetailSection.tsx / HeaderHighlight.tsx + its own test + changeset). address / location / geolocation are not in the shared exclusion, so #4222's new routing is unaffected, and it is pinned as a control here.

Re-authoring a credential is unchanged and still belongs in the record form, which has the widget for it (PasswordField).


Generated by Claude Code

…d page

`InlineFieldInput` has no branch for `password` or `secret`, so both reached the
terminal raw text input at the end of the component. Both types are masked on
read, so the value the row could seed an editor with was never the credential:
it was the payload's placeholder — a server-side mask, or, for `secret`, an
opaque reference into an encrypted store (ADR-0100). The detail hosts' gate
excluded only readonly / computed / system fields, so the pencil appeared, the
placeholder was rendered in clear in a `type="text"` box, and committing the
row wrote it back verbatim over the credential.

The decision was already written down one package over: `INLINE_EXCLUDED_FIELD_TYPES`
excludes both types with exactly this reasoning and the grid honours it through
`isInlineExcludedFieldType()`. Both detail hosts now consult that same
alias-aware contract — a narrow-only union of the authored and the object type,
matching the computed gate (#3355) — instead of a second hand-maintained list.

Consulting the set closes the container family (`object`/`composite`/`record`/
`grid`/`repeater`/`vector`) and the spec `autonumber` spelling with it. The
binary/attachment family is exempt and keeps its detail editor: it is in the
shared set for a grid-cell reason, while `InlineFieldInput` routes it to the
form's own upload widgets. The exemption is pinned against that routing.

Fixes #4221

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
@vercel

vercel Bot commented Aug 11, 2026 •

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 11, 2026 5:43am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.3 KB 350 KB
Entry file index-Uzohd74h.js —
Status PASS —

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.88KB 3.25KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.65KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 486.47KB 107.53KB
core (index.js) 3.04KB 1.15KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 143.81KB 37.39KB
fields (index.js) 228.33KB 56.58KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.65KB 1.06KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 9.48KB 3.27KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 4.52KB 1.96KB
layout (index.js) 38.87KB 10.80KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 45.23KB 12.45KB
plugin-charts (index.js) 61.52KB 17.49KB
plugin-chatbot (index.js) 180.33KB 42.79KB
plugin-dashboard (index.js) 118.52KB 30.68KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 238.53KB 59.65KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 114.58KB 27.68KB
plugin-gantt (index.js) 164.14KB 39.98KB
plugin-grid (index.js) 187.97KB 49.90KB
plugin-kanban (index.js) 48.60KB 13.41KB
plugin-list (index.js) 110.18KB 26.70KB
plugin-map (index.js) 17.00KB 5.32KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.58KB 10.58KB
plugin-timeline (index.js) 26.21KB 7.52KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.03KB 20.55KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 23.71KB 7.95KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.23KB 0.66KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 2.71KB 1.34KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@yinlianghui
yinlianghui marked this pull request as ready for review August 11, 2026 05:43
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit 5e2e9fa Aug 11, 2026
21 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-4221-inline-credential-gate branch August 11, 2026 05:44
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.3 KB 350 KB
Entry file index-CVKWc6gY.js —
Status PASS —

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.88KB 3.25KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.65KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 488.60KB 108.25KB
core (index.js) 3.04KB 1.15KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 145.46KB 37.89KB
fields (index.js) 228.33KB 56.58KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.65KB 1.06KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 9.48KB 3.27KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 4.52KB 1.96KB
layout (index.js) 38.98KB 10.85KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 45.23KB 12.45KB
plugin-charts (index.js) 61.52KB 17.49KB
plugin-chatbot (index.js) 180.33KB 42.79KB
plugin-dashboard (index.js) 118.58KB 30.71KB
plugin-designer (index.js) 210.85KB 42.64KB
plugin-detail (index.js) 238.53KB 59.65KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 114.58KB 27.68KB
plugin-gantt (index.js) 164.14KB 39.98KB
plugin-grid (index.js) 187.97KB 49.90KB
plugin-kanban (index.js) 48.60KB 13.41KB
plugin-list (index.js) 110.31KB 26.76KB
plugin-map (index.js) 17.00KB 5.32KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.58KB 10.58KB
plugin-timeline (index.js) 26.21KB 7.52KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.03KB 20.55KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 23.71KB 7.96KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.23KB 0.66KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 2.71KB 1.34KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Aug 17, 2026
…idget (objectstack-ai#4244)

InlineFieldInput's type switch ends in a raw text input, and everything it has
no branch for landed there: displayed through coerceToSafeValue and written
back as whatever was typed, a bare string.

Two damage classes survived objectstack-ai#4216 (structured objects) and objectstack-ai#4228 (containers +
credentials, closed at the host gate). Array-valued fields (tags, checkboxes,
an options-less multi picklist) were offered as "a, b" — coerceToSafeValue
joins arrays — and saved as that string. Type-lossy scalars (toggle, slider,
progress, rating, radio) round-tripped through String(), so a boolean column
received "true", a numeric one "42", and radio accepted any free-typed value
its option list never offered.

Types the switch already routes keep their editors, so no working path churns.
Everything else the fields package can edit inline now falls back to
FieldEditWidget — the form's own control, json to the code editor included —
and only genuinely string-valued types keep the plain input. The design calls
inside the array class resolve inside that delegation, which is the point of
delegating rather than routing type by type.

The lasting deliverable is the drift guard: every member of the form widget map
UNION the spec FieldType enum must be exactly one of routed / excluded /
delegated / benign, and the declared bucket is checked against what the
component really renders. A new type in none of the four is red, instead of
inheriting the value-destroying default in silence — this family has produced
six cards from that one cause.

@object-ui/fields: the four fixed-option widgets no longer clear the stored
value when no options are authored. An empty offered set had two opposite
causes — cascaded to zero (clear, ADR-0058) and never configured (nothing to
decide) — and the second deleted the value on MOUNT, which the delegation would
have staged straight into the record draft. FieldEditWidget also forwards
autoFocus so an inline host's caret lands on the widget's own control.

Fixes objectstack-ai#4220


Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3

Co-authored-by: Claude <noreply@anthropic.com>
os-zhuang pushed a commit that referenced this pull request Oct 3, 2026
…n a multi-line textarea (objectui#11541)

A markdown row offered no pencil and no editor in edit mode: the detail
hosts consult the fields package's shared inline-edit exclusion, and
markdown is in it. #4228 recorded the detail-row reason as "heavy editor;
a one-line text box is lossy". The row now routes markdown to the fields
package's multi-line TextAreaField through DETAIL_ROUTED_INLINE_TYPES, the
carve-out the upload widgets already use, so neither reason applies. The
grid cell keeps the exclusion; html and richtext stay excluded.

The INLINE_PLAIN_TEXT_FIELD_TYPES docblock no longer says the hosts never
open an editor for markdown, and records that the one-line terminal input
strips line breaks, which also holds for a multi-line textarea value.

Pins: a markdown editor seeded and emitted byte for byte with blank lines
and a trailing newline, both detail hosts, a save that reads back
unchanged, html/richtext still open none, and the grid gate still refuses
markdown. The type-coverage drift guard moves markdown to routed.

Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to 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.

Inline-editing a password / secret field on the detail page renders the mask as the value and writes it straight back

2 participants