Found during the spelling-sensitivity sweep for #4219 (the autonumber / auto_number gap in TEXTUAL_REF_FALLBACK_TYPES); filed unassigned per Prime Directive #10. Same defect shape, different type family — reported rather than fixed inside #4219 because it spans three packages and carries an adjacent design call.
The spelling
@objectstack/spec's FieldType enum spells it richtext — one word, no separator. The alias table agrees and knows no other spelling:
packages/fields/src/field-type-alias.ts:72
richtext: 'field:richtext', // WYSIWYG rich-text editor
There is no rich_text and no rich-text anywhere in the alias table or the spec enum, so neither is a spelling any producer emits.
The sets that miss it
Each of these is matched by RAW spelling, so a member nothing emits is a dead entry AND a missing live one — the set silently does nothing for the type it names:
| where |
member |
effect on a spec-spelled richtext field |
packages/plugin-detail/src/RelatedList.tsx:979 — SKIP_TYPES |
rich_text |
not skipped: the field is auto-derived into a related-list column, so raw markup renders in a table cell. The set's own comment says "Only types with no useful tabular rendering stay excluded" |
packages/plugin-detail/src/autoLayout.ts:38 — WIDE_FIELD_TYPES |
rich-text, field:rich-text |
never spans full width in a multi-column detail section, unlike markdown / html beside it |
packages/plugin-form/src/autoLayout.ts:45 — WIDE_FIELD_TYPES |
rich-text |
same, on the form |
packages/app-shell/src/views/RecordDetailView.tsx:136 — SECONDARY_FIELD_TYPES |
rich-text |
not treated as a secondary field |
The convention that already exists, one file away from two of them, is to carry every spelling:
packages/plugin-form/src/deriveMasterDetail.ts:339
'textarea', 'richtext', 'html', 'markdown', 'rich-text',
Why the pin does not catch it
packages/plugin-form/src/__tests__/autoLayout.test.ts:30
expect(isWideFieldType('rich-text')).toBe(true);
That assertion is green because the set contains the string it asks about — not because any field ever reaches it with that type. It is the shape #4219's sweep was looking for: a pin whose fixture spells something the schema never emits, so it keeps passing while the rule is dead for every real field.
Design call attached (why this is not a one-word patch)
Two decisions belong to whoever picks this up, and both change the diff:
- Does
markdown belong in RelatedList's SKIP_TYPES? It is absent entirely, and the stated rationale ("no useful tabular rendering") applies to it exactly as it does to html. Adding richtext without deciding this leaves the same class of gap one type over.
- Drop the dead spellings or keep them?
deriveMasterDetail keeps both, which is defensive and honest about legacy metadata; the alternative is to spell only what the spec spells and let the alias table be the single place aliases live. Whichever is chosen should be applied to all four sets at once — fixing only the two in plugin-detail would make the detail page and the form disagree about the same field, which is worse than a uniform gap.
Acceptance criteria
- A field typed
richtext (the spec spelling) is excluded from auto-derived related-list columns, spans full width in both autoLayout implementations, and is treated as secondary in RecordDetailView.
- Each fix carries a pin that fails when the spelling is removed — asserted through the behavior (a rendered column set / a resolved span), not only through
isWideFieldType('...') on a string the set already holds.
- The
markdown question above is answered explicitly, in the code or in the PR body.
Generated by Claude Code
Found during the spelling-sensitivity sweep for #4219 (the
autonumber/auto_numbergap inTEXTUAL_REF_FALLBACK_TYPES); filed unassigned per Prime Directive #10. Same defect shape, different type family — reported rather than fixed inside #4219 because it spans three packages and carries an adjacent design call.The spelling
@objectstack/spec'sFieldTypeenum spells itrichtext— one word, no separator. The alias table agrees and knows no other spelling:There is no
rich_textand norich-textanywhere in the alias table or the spec enum, so neither is a spelling any producer emits.The sets that miss it
Each of these is matched by RAW spelling, so a member nothing emits is a dead entry AND a missing live one — the set silently does nothing for the type it names:
richtextfieldpackages/plugin-detail/src/RelatedList.tsx:979—SKIP_TYPESrich_textpackages/plugin-detail/src/autoLayout.ts:38—WIDE_FIELD_TYPESrich-text,field:rich-textmarkdown/htmlbeside itpackages/plugin-form/src/autoLayout.ts:45—WIDE_FIELD_TYPESrich-textpackages/app-shell/src/views/RecordDetailView.tsx:136—SECONDARY_FIELD_TYPESrich-textThe convention that already exists, one file away from two of them, is to carry every spelling:
Why the pin does not catch it
That assertion is green because the set contains the string it asks about — not because any field ever reaches it with that type. It is the shape #4219's sweep was looking for: a pin whose fixture spells something the schema never emits, so it keeps passing while the rule is dead for every real field.
Design call attached (why this is not a one-word patch)
Two decisions belong to whoever picks this up, and both change the diff:
markdownbelong inRelatedList'sSKIP_TYPES? It is absent entirely, and the stated rationale ("no useful tabular rendering") applies to it exactly as it does tohtml. Addingrichtextwithout deciding this leaves the same class of gap one type over.deriveMasterDetailkeeps both, which is defensive and honest about legacy metadata; the alternative is to spell only what the spec spells and let the alias table be the single place aliases live. Whichever is chosen should be applied to all four sets at once — fixing only the two inplugin-detailwould make the detail page and the form disagree about the same field, which is worse than a uniform gap.Acceptance criteria
richtext(the spec spelling) is excluded from auto-derived related-list columns, spans full width in bothautoLayoutimplementations, and is treated as secondary inRecordDetailView.isWideFieldType('...')on a string the set already holds.markdownquestion above is answered explicitly, in the code or in the PR body.Generated by Claude Code