Finding (observation, awaiting first grading). Filed unlabelled and unassigned — grading and domain:* belong to triage.
Surfaced by the dev on PR #8373 (card #7083) as an out-of-scope note and deliberately not acted on there. ⚠️ Every reading below is this seat's own, re-measured on origin/main ca394272 — none is inherited from that report, and one of the report's three sites did not survive the re-measurement (see 〈The third site, measured and excluded〉).
The asymmetry
richtext is a live registry key: packages/fields/src/index.tsx:2769 resolves 'richtext' to RichTextField, and it is declared in known-schema-types.ts, reports.ts and the field-type alias table. It is one of three registry keys served by ONE widget — markdown, html, richtext.
A max_length authored on a richtext field is enforced at submit and invisible everywhere else:
| stage |
reads max_length on a richtext field? |
site |
| submit-time validation |
✅ yes |
packages/fields/src/index.tsx:2565 buildValidationRules |
native maxLength stop + character counter + aria-describedby |
❌ no |
packages/plugin-form/src/ObjectForm.tsx:784 |
| default cap when the author declared none |
❌ no |
packages/plugin-form/src/EmbeddableForm.tsx:148 |
So the field silently accepts typing past its own limit, shows no counter, announces nothing to a screen reader — and then fails validation on submit, after the person has written the text. That is the worst ordering of the three possible ones.
Site 1 — ObjectForm.tsx:784, the maxLength-attribute forwarding guard
if (field.type === 'text' || field.type === 'textarea' || field.type === 'markdown' || field.type === 'html') {
formField.maxLength = (field as any).maxLength ?? field.max_length;
formField.minLength = (field as any).minLength ?? field.min_length;
richtext is absent. Firing control: markdown and html ARE present in the same literal, so the instrument that reports richtext absent is the same one that reports its two siblings present — the absence is a reading, not an empty search.
formField.maxLength is what packages/components/src/renderers/form/form.tsx turns into three separate consumer-visible things — the native attribute on both textareas (:761, :781, :812, :832), the character counter (:767, gated maxLength ? …), and the aria-describedby wiring (:745, :855). All three are lost together.
Site 2 — EmbeddableForm.tsx:148, DEFAULT_MAX_LENGTH
const DEFAULT_MAX_LENGTH: Record<string, number> = {
text: 200, email: 254, url: 2048, phone: 32,
textarea: 5000, markdown: 5000, html: 5000,
};
richtext absent; markdown: 5000 and html: 5000 present as the firing control. The consumer at :187-191 is const cap = DEFAULT_MAX_LENGTH[t]; if (!cap) return f; — so absence is not a zero cap, it is no cap at all, applied silently. A public embeddable form with a richtext field takes unbounded input where its markdown twin stops at 5000.
Why the exclusion is not justified by the control type
The obvious defence would be that an HTML maxlength attribute is meaningless on a rich-text editor. It does not hold here. RichTextField renders a plain <Textarea> — the same control as its siblings:
packages/fields/src/widgets/RichTextField.tsx:98 — the <Textarea> itself
:24 — "The <Textarea> below is this widget's ONE focusable control"
:183 — "For now, this is a simple textarea. A full implementation would use …"
Three registry keys, one widget, one <Textarea>. Two of the three get the cap.
The contrast site, measured rather than assumed
buildValidationRules (packages/fields/src/index.tsx:2565) reads (field as any).maxLength ?? field.max_length with no field-type gate. Measured: the only two field.type comparisons in its whole body are === 'email' and === 'url', both attaching pattern rules, neither touching the length rules. Both form producers call it on every field they build.
⇒ Submit-time enforcement is generic and already correct. The defect is entirely in the two hand-written type lists, which is what makes this cheap to fix and easy to get wrong again.
The third site, measured and excluded
PR #8373's report grouped a third enumeration with these two — DESIGNER_FIELD_TYPES (packages/types/src/designer.ts:734) — as "three pre-existing enumerations that stop at two of the widget's three registry keys". Measured against the widget registry, that grouping does not hold, so it is not part of this card:
- registry keys ABSENT from
DESIGNER_FIELD_TYPES — 12: auto_number checkboxes grid master_detail multiselect object radio richtext summary tags user vector
DESIGNER_FIELD_TYPES entries with NO widget in the registry — 6: address autonumber code color rating slider
That is a curated designer palette on its own axis, not an enumeration of the widget's registry keys. richtext sitting outside it is one of twelve and says nothing about this widget. If there is a question there it is "does the designer palette drift from the registry", which is a 12-key card and a different lane. (Noted in passing, not filed: the registry spells auto_number, the palette autonumber.)
A spelling hypothesis that was checked and came back negative
Recorded so the next reader does not re-derive it. #4831's list uses field:-prefixed spellings (field:textarea, field:markdown, field:richtext) while site 1 uses bare ones, which looks like a second defect. It is not. The two lists sit at different stages of the same file: site 1 (:784) reads the raw object-metadata field.type before mapFieldTypeToFormType, where bare spec spellings are correct; the stamping list (:1448) reads the already-mapped form-field type, where prefixed spellings are correct. Site 1's spelling family is right — it is simply missing one member.
Same class, same file, and a root cause that was named and left standing
#4831 — "mobile.fullscreenLongText 漏掉 field:richtext" — is this defect's twin: the same field type, the same file, the same shape (a hand-written type list omitting the third of one widget's three registry keys). It was fixed by PR #4839, and the fix's own docblock now sits at ObjectForm.tsx:1404-1412 recording the lesson verbatim: "field:richtext was missing from this list until objectui#4831".
⚠️ That docblock is ~660 lines below a list with the same gap. The file carries a written memory of this exact class and the class recurred inside it.
#4831 also asked, in its own body, the question that would have prevented this one:
顺带一并判断(属于同一次决定,不要只补一个字面量):清单是否应该改成「凡是解析到长文本 widget 的类型」而不是四个手写字面量 —— 手写清单正是本条与 #4250 的共同成因。
It was answered by patching one literal. This card is the next instance of the root cause that question named and its fix declined to remove. #4250 (richtext missed in four layout/column sets) is the third member.
⇒ Whoever takes this should decide the list question, not just add 'richtext' twice. Two more hand-written lists with richtext added are two more places for the fourth instance.
Not a duplicate of
All six neighbours are closed, and none covers either site:
For the implementer
⛔ Do not pin this by asserting the literals are in the list — that is the 假绿 form #4250 named and #4831 warned about explicitly. Pin it by behaviour: an object field of type: 'richtext' with max_length authored, rendered through ObjectForm, asserting the native attribute and the counter are present; and an EmbeddableForm richtext field with no authored cap, asserting the default applies. Removing 'richtext' from either list must turn both red.
Refs: #4831 / PR #4839 (the twin, same file) · #4250 (the family) · #5201 · #5253 · #3439 · #7083 / PR #8373 (where it surfaced).
Finding (observation, awaiting first grading). Filed unlabelled and unassigned — grading and
domain:*belong to triage.Surfaced by the dev on PR #8373 (card #7083) as an out-of-scope note and deliberately not acted on there.⚠️ Every reading below is this seat's own, re-measured on
origin/mainca394272— none is inherited from that report, and one of the report's three sites did not survive the re-measurement (see 〈The third site, measured and excluded〉).The asymmetry
richtextis a live registry key:packages/fields/src/index.tsx:2769resolves'richtext'toRichTextField, and it is declared inknown-schema-types.ts,reports.tsand the field-type alias table. It is one of three registry keys served by ONE widget —markdown,html,richtext.A
max_lengthauthored on arichtextfield is enforced at submit and invisible everywhere else:max_lengthon arichtextfield?packages/fields/src/index.tsx:2565buildValidationRulesmaxLengthstop + character counter +aria-describedbypackages/plugin-form/src/ObjectForm.tsx:784packages/plugin-form/src/EmbeddableForm.tsx:148So the field silently accepts typing past its own limit, shows no counter, announces nothing to a screen reader — and then fails validation on submit, after the person has written the text. That is the worst ordering of the three possible ones.
Site 1 —
ObjectForm.tsx:784, the maxLength-attribute forwarding guardrichtextis absent. Firing control:markdownandhtmlARE present in the same literal, so the instrument that reportsrichtextabsent is the same one that reports its two siblings present — the absence is a reading, not an empty search.formField.maxLengthis whatpackages/components/src/renderers/form/form.tsxturns into three separate consumer-visible things — the native attribute on both textareas (:761,:781,:812,:832), the character counter (:767, gatedmaxLength ? …), and thearia-describedbywiring (:745,:855). All three are lost together.Site 2 —
EmbeddableForm.tsx:148,DEFAULT_MAX_LENGTHrichtextabsent;markdown: 5000andhtml: 5000present as the firing control. The consumer at:187-191isconst cap = DEFAULT_MAX_LENGTH[t]; if (!cap) return f;— so absence is not a zero cap, it is no cap at all, applied silently. A public embeddable form with arichtextfield takes unbounded input where itsmarkdowntwin stops at 5000.Why the exclusion is not justified by the control type
The obvious defence would be that an HTML
maxlengthattribute is meaningless on a rich-text editor. It does not hold here.RichTextFieldrenders a plain<Textarea>— the same control as its siblings:packages/fields/src/widgets/RichTextField.tsx:98— the<Textarea>itself:24— "The<Textarea>below is this widget's ONE focusable control":183— "For now, this is a simple textarea. A full implementation would use …"Three registry keys, one widget, one
<Textarea>. Two of the three get the cap.The contrast site, measured rather than assumed
buildValidationRules(packages/fields/src/index.tsx:2565) reads(field as any).maxLength ?? field.max_lengthwith no field-type gate. Measured: the only twofield.typecomparisons in its whole body are=== 'email'and=== 'url', both attachingpatternrules, neither touching the length rules. Both form producers call it on every field they build.⇒ Submit-time enforcement is generic and already correct. The defect is entirely in the two hand-written type lists, which is what makes this cheap to fix and easy to get wrong again.
The third site, measured and excluded
PR #8373's report grouped a third enumeration with these two —
DESIGNER_FIELD_TYPES(packages/types/src/designer.ts:734) — as "three pre-existing enumerations that stop at two of the widget's three registry keys". Measured against the widget registry, that grouping does not hold, so it is not part of this card:DESIGNER_FIELD_TYPES— 12:auto_numbercheckboxesgridmaster_detailmultiselectobjectradiorichtextsummarytagsuservectorDESIGNER_FIELD_TYPESentries with NO widget in the registry — 6:addressautonumbercodecolorratingsliderThat is a curated designer palette on its own axis, not an enumeration of the widget's registry keys.
richtextsitting outside it is one of twelve and says nothing about this widget. If there is a question there it is "does the designer palette drift from the registry", which is a 12-key card and a different lane. (Noted in passing, not filed: the registry spellsauto_number, the paletteautonumber.)A spelling hypothesis that was checked and came back negative
Recorded so the next reader does not re-derive it. #4831's list uses
field:-prefixed spellings (field:textarea,field:markdown,field:richtext) while site 1 uses bare ones, which looks like a second defect. It is not. The two lists sit at different stages of the same file: site 1 (:784) reads the raw object-metadatafield.typebeforemapFieldTypeToFormType, where bare spec spellings are correct; the stamping list (:1448) reads the already-mapped form-field type, where prefixed spellings are correct. Site 1's spelling family is right — it is simply missing one member.Same class, same file, and a root cause that was named and left standing
#4831 — "
mobile.fullscreenLongText漏掉field:richtext" — is this defect's twin: the same field type, the same file, the same shape (a hand-written type list omitting the third of one widget's three registry keys). It was fixed by PR #4839, and the fix's own docblock now sits atObjectForm.tsx:1404-1412recording the lesson verbatim: "field:richtextwas missing from this list until objectui#4831".#4831 also asked, in its own body, the question that would have prevented this one:
It was answered by patching one literal. This card is the next instance of the root cause that question named and its fix declined to remove. #4250 (
richtextmissed in four layout/column sets) is the third member.⇒ Whoever takes this should decide the list question, not just add
'richtext'twice. Two more hand-written lists withrichtextadded are two more places for the fourth instance.Not a duplicate of
All six neighbours are closed, and none covers either site:
mobile.fullscreenLongText漏掉field:richtext:spec 拼法的 richtext 字段永远拿不到全屏编辑,而 widget 早就在读那个 flag #4831 / PR fix(plugin-form): stamp mobile.fullscreenLongText onto field:richtext too (#4831) #4839 — same class, same file, themobile.fullscreenLongTextstamping list. Different list.richtexttype is spelled three ways across four layout/column sets, and the spec spells it none of them #4250 —richtextmissed in four layout/column sets. Same family, different sets.max_length拼法拿不到任何 maxlength 上限,只在 DOM 上留下一个失效属性 #5201 / 内建 form 的default回退分支有和 #5201 完全相同的 ceiling 缺陷:max_length拿不到上限,只在 DOM 上留下失效属性 #5253 — the legacymax_lengthspelling reached no ceiling and left a dead DOM attribute. About the spelling on types already in the list; this card is about a type not in the list. The dual-read those closed is present and working at:789.buildSectionsdropped a field-levelmaxLengthoverride. Different producer.form.tsx:767) and is one of the three thingsrichtextdoes not reach.For the implementer
⛔ Do not pin this by asserting the literals are in the list — that is the 假绿 form #4250 named and #4831 warned about explicitly. Pin it by behaviour: an object field of
type: 'richtext'withmax_lengthauthored, rendered throughObjectForm, asserting the native attribute and the counter are present; and anEmbeddableFormrichtextfield with no authored cap, asserting the default applies. Removing'richtext'from either list must turn both red.Refs: #4831 / PR #4839 (the twin, same file) · #4250 (the family) · #5201 · #5253 · #3439 · #7083 / PR #8373 (where it surfaced).