Repository navigation
feat(fields): isMaskedFieldType() is the one masked-type authority; plugin-detail reads it (objectui#8686) - #10568
Conversation
…eads it (objectui#8686) `@object-ui/fields` gains `MASKED_FIELD_TYPES` and `isMaskedFieldType()`, the read-side twin of `isInlineExcludedFieldType()`. The standard cell table's masked entries are built from the set, and the predicate reads the live cell registry: the mask registered under a new type answers true, a shipped mask replaced by one of the package's own renderers answers false, and a shipped mask replaced by an unreadable host component keeps the declared answer. `plugin-detail`'s `isMaskedDetailFieldType` drops its local two-member copy and asks the authority, so a masked type registered in `fields` refuses the copy affordance with no edit there. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
… in the README (objectui#8686) One paragraph beside the `listCellRendererTypes()` section: what the predicate answers, the live-registry reading (a package-renderer override unmasks, an opaque host override keeps the declared answer), and the host idiom for adding a masked type. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…8686) Two clauses over-stated, per the contract review of objectui#10568: - "none of them masks, so the cell now shows the value" held only for `TextCellRenderer`. Several package renderers draw a literal or a dash instead of the value. The docblock, README and changeset now say the predicate answers `false` because none of them is the mask, and that the cell draws what that renderer draws (for `TextCellRenderer`, the value). - The README's agreement with `getCellRenderer` now carries the qualifier "with nothing overridden at runtime". An opaque host override of a declared type is the one case where the two differ, as the paragraph itself says. Prose only: no code, test or bump-level change. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Fixes #8686
Clause-②: yes
What
@object-ui/fieldsnow answers "is this field type's cell drawn as a mask?" in one place, and@object-ui/plugin-detailasks it instead of keeping its own list. This carries out the ruling on the card (comment5644350822, decision batch 122 item 4, letter A).@object-ui/fields(the package barrel issrc/index.tsxitself):MASKED_FIELD_TYPES, a READONLYSET of STRING, todaypasswordandsecret; andisMaskedFieldType(fieldType). They are the read-side twin ofINLINE_EXCLUDED_FIELD_TYPES/isInlineExcludedFieldType().MASKED_FIELD_TYPES. Adding a type to the set masks its cell and turns the predicatetruein the same edit.plugin-detail.fieldEnrichment.tshas no local two-member Set any more.isMaskedDetailFieldTypecallsisMaskedFieldType()per type and keeps the narrow-only union of the view type and the object type (objectui#3355).DetailSection.tsxis untouched.Protocol-first reading (ruling item 3): the spec declares no masked-type fact
Read before any edit:
@objectstack/spec17.4.0 (src,dist,api-surface) and objectstackorigin/mainat95ab93f5(packages/spec) have no exported set, flag or predicate naming which field types are masked, sensitive or credential-bearing.SENSITIVE_FIELD*,CREDENTIAL_FIELD_TYPES,SECRET_FIELD_TYPES,isMaskedFieldType,isSensitiveFieldType,MASK_ON_READ.git greponorigin/mainreturned exit 1 with 0 lines. The positive control is the samegit grepform forBOUNDED_STRING_FIELD_TYPES: ReadonlySet, which returned exit 0 and one hit. So the spec does use the named field-type-subset pattern; it just has no subset for this fact.FieldTypeenum comments:passwordandsecretare "masked ... on read".SECRET_MASKonorigin/main. That is the mask STRING, not a set of types.collectMaskedReadFieldschecksdef.type === 'secret', and'password'unless the object ismanagedBy: 'better-auth'.⇒ Per the ruling,
fieldsowns the set for now. Thedomain:speclift is in my report for the seat to file; it is not filed from this PR. The docblock onMASKED_FIELD_TYPESrecords the rule: if the protocol comes to declare this fact, the set derives from it.Design: live registration first, declared set as fallback (ruling item 1)
What can be observed without a new concept:
getCellRendereruses: the runtime registry, then the standard table.MaskedCellRenderer, since objectui#10529. Identity comparison is therefore exact.getCellRenderer('password')) without it being exported.isMaskedFieldType(t):ttrueregisterFieldRenderer('api_token', getCellRenderer('password'))tis not inMASKED_FIELD_TYPESfalsetis declared, but a host replaced its mask with one of THIS package's renderers (e.g.TextCellRenderer)falseTextCellRenderer, the value)tis declared, but a host replaced its mask with ITS OWN componenttrueRejected alternative: pure identity (
getCellRenderer(t) === mask). It is simpler, but it answersfalsefor a host's own custom mask onpassword. The detail page would then offer one-click copy of a credential that the screen hides, which is the disclosure direction that objectui#8440 shut, and the regression this card exists to prevent. The old mirror refused copy in that case, so pure identity would have been a regression there. The chosen design never answersfalsefor a declared type unless it can see the value is shown.Other properties:
field:passwordfalls toTextCellRendererand is not masked (pinned).getCellRenderer, it does not report retired spellings, same asisInlineExcludedFieldType().Pins
packages/fields/src/__tests__/isMaskedFieldType-8686.test.tsx(adomproject file,isolate: true, because the RUNTIME cases mutate the registry)trueAND its cell draws••••••with the raw value absent from the DOM.textanswersfalseand draws the same probe value.undefined,'', an unregistered spelling andfield:passwordall answerfalse.listCellRendererTypes()reading, the predicate agrees with the resolver on every type (trueexactly where the resolved renderer is the mask). With nothing registered at runtime, the masked types are exactly the declared set.packages/plugin-detail/src/__tests__/DetailSection.maskedTypeAuthority-8686.test.tsx(the ruling's pin)registerFieldRenderer. Its spelling appears nowhere in plugin-detail, and the detail row then refuses the copy on all desktop paths (row click, Enter, Space, hover button) with no plugin-detail edit.passwordmask replaced withTextCellRenderershows the value and copies it. The control leg shows the shipped mask refusing.Ablation (ruling pin (b))
fieldEnrichment.ts, in place of theisMaskedFieldType(fieldType)call. It was written with objectstack'sscripts/ablation-replace.mjsin wrap mode, under the verify lock.7fd637ea4e1atoae9a46f0874a.anchor=0 inlined=1.Tests 2 failed | 22 passed (24).7fd637ea4e1aequals the HEAD blob, andgit diff HEADon the path is empty.Verification, all at head
a47de0e0cpnpm exec vitest run packages/fields/ packages/plugin-detail/from the repo root:Test Files 408 passed | 2 skipped (410),Tests 5351 passed | 15 skipped (5366).turbo run build --filter='@object-ui/plugin-detail^...' --concurrency=2: 11/11 tasks.type-checkon@object-ui/fieldsand@object-ui/plugin-detail: bothDone.--listFiles, one hit each).dist, anddist/index.d.tsnow declares both symbols, so plugin-detail type-checked against the rebuilt declarations.pnpm check:control-bytes: OK.pnpm check:new-line-citations: 0 new.node scripts/check-changeset-presence.mjs: OK, 1 changeset.node scripts/check-changeset-no-major.mjs: OK.pnpm check:changeset-claims: exit 0.pnpm check:spec-symbols: OK.pnpm check:vi-mock-inherit: 828 inherit, 0 auto-mocked.check:test-path-roots,check:unreferenced-sources,check:self-import,check:comment-mask-corpus: all OK.pnpm check:readme-exports: NOT MEASURED as a whole. It exits 1 on a collapsed population because 25 packages are unbuilt in this worktree.--listover what was built judges the 5packages/fields/README.mdself-imports as real. This diff touches no README and only adds exports.--no-inline-config, JSON) over the 4 touched source and test files, with per-rule counts compared to the base versions:index.tsx:react-refresh/only-export-componentswarn 93 to 95 (the two new non-component exports, the same shape as the file's existing ones);no-explicit-any44 to 44;no-unused-vars1 to 1.fieldEnrichment.ts:no-explicit-any5 to 5.eslint.config.js, so the diff cannot move a verdict in an untouched file.Acceptance notes
packages/fields/README.mdparagraph beside thelistCellRendererTypes()section is outside this claim's declared file surface. It is named in the report for the seat to decide: a patch round with a widened surface, or a docs-only follow-up.ObjectGrid(desktop), apasswordcolumn draws the mask, yet Ctrl+C on the focused cell writes the RAW value to the clipboard. The cause is DataTable's cellhandleCellKeyDown, which copiesrow[columnKey]. This is the objectui#8440 defect class on the grid, and it is a natural second consumer ofisMaskedFieldType(). ⛔ It is not addressed here. The probe was throwaway and was deleted.useCellClipboardin plugin-grid also copies raw values but has no in-repo caller. It is recorded only.isMaskedFieldinDetailSection.tsxstill namespassword/secretas the masked types. That is still true of the shipped defaults, and the file was left untouched per the dispatch.packages/fields/src/index.tsx,fieldEnrichment.tsorDetailSection.tsx. Thefieldshunks sit only besideMaskedCellRendererand in the table'spassword/secretentries. They are disjoint fromImageCellRenderer(objectui#10493's live claim).Implemented by the
domain:uiseat 1 dispatch, sessionhttps://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC.Generated by Claude Code