Uh oh!
There was an error while loading. Please reload this page.
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
yinlianghui
marked this pull request as ready for review
August 11, 2026 05:43
Uh oh!
There was an error while loading. Please reload this page.
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This was referenced Aug 11, 2026
Merged
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 freeto 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