Repository navigation
fix(detail): a credential field is never inline-editable on the record page - #4228
Merged
Merged
Conversation
…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
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This was referenced Aug 11, 2026
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This was referenced Aug 11, 2026
Merged
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4221
A
passwordorsecretfield on the record detail page is no longer inline-editable: no pencil, no double-click affordance, no editor — on both hosts (theDetailSectionbody row and theHeaderHighlightstrip).The defect, as measured
InlineFieldInputhas 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 —
getCellRendererreturns a fixed bullet run forpasswordandsecretalike — so the value the row could hand an editor was never the credential. It was whatever the payload carries: a server-side mask, or, forsecret, an opaque reference into an encrypted store (ADR-0100). That placeholder was seeded into a plaininput[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:
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
valueentirely. So the box shows whatever the API actually sent for that field, and the write-back destroys it. Forsecretthat 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_TYPESin@object-ui/fieldsexcludes both types with exactly this reasoning, and the grid honours it throughisInlineExcludedFieldType()(plugin-grid/src/inline-edit-options.ts). Both detail hosts now consult that same alias-aware contract via a newisInlineExcludedDetailFieldType()infieldEnrichment.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.tsxalready doesexport * from './FieldEditWidget', which is howplugin-gridimports the helper today.packages/fieldsis untouched, so the changeset is@object-ui/plugin-detailalone.The helper is a narrow-only union of the authored view type and the object type, matching
isComputedFieldTypeunder objectui#3355: an authored displaytypecan lock a field but never unlock one. Animageauthored over an objectsecretstays 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.passwordsecretobjectcompositeobjectrecordobjectgridrepeatergridvectormarkdownhtmlrichtextobject-reffilter-conditionrecipient-pickerautonumberauto_numberauto_numberspellingauto_numberformulasummaryfileInlineFieldInputroutesFileFieldvideofileaudiofileimageInlineFieldInputroutesImageFieldavatarInlineFieldInputroutesAvatarFieldsignatureInlineFieldInputroutesSignatureFieldWhy 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:
InlineFieldInputroutesimage/avatar/signature/file(and thevideo/audiospellings) 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 exemption is not a second free-floating list:
inlineCredentialGate.test.tsxpins every entry twice — it must be a real member of the shared set, andInlineFieldInputmust 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)
object/composite/record/grid/repeater/vectorall fall out of the shared set, and are pinned no-longer-editable in the blast-radius block. Inline edit still destroys array-valued and container field values on the detail page (the rest of the #4216 fall-through sweep) #4220's remaining half is the routing-design work: arrays, type-lossy scalars that stay inside the set's "editable" verdict (json,code,color,qrcode,tags,time, … still reach the detail text box), and the delegate-vs-route ruling.autonumber(the spec spelling) is inline-editable on the detail page, so a user can overwrite a machine-generated value #4219 — spelling half is closed as a ride-along. The spec spells itautonumber; the detail computed gate (TEXTUAL_REF_FALLBACK_TYPES) only carriesauto_number, so anautonumberfield was inline-editable.isInlineExcludedFieldTyperesolves the alias, so it is now excluded. Whether A field typedautonumber(the spec spelling) is inline-editable on the detail page, so a user can overwrite a machine-generated value #4219 also wants it in the computed gate proper is left to that card.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.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 toorigin/mainand is unrelated to this change: its fixture authorslayout: 'custom', arecord:detailsproperty@objectstack/spec17.0.0-rc.6 removed (objectstack#6946, ADR-0087 D2), sosafeParsenow rejects it withexpected: "never".mainis red on it as of6314e87f2. Reported onrecord: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.eslinton the touched files — 0 errors.pnpm check:control-bytes— OK (3924 tracked files); plus a targeted self-scan of the touched files, no hits.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 isInlineFieldInput.tsx+ its own test + changeset; this isfieldEnrichment.ts/DetailSection.tsx/HeaderHighlight.tsx+ its own test + changeset).address/location/geolocationare 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