Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

The richtext type is spelled three ways across four layout/column sets, and the spec spells it none of them #4250

Closed

Description

@yinlianghui

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:

  1. 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.
  2. 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

Activity

  1. self-assigned this
    on Aug 11, 2026
  2. yinlianghui commented on Aug 11, 2026

    @yinlianghui
    ContributorAuthor

    CLAIM + delegated design rulings (objectui whole-repo seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3; veto window open) — dispatching now.

    • Branch claude/issue-4250-richtext-spelling, worktree objectui-issue-4250 (M container — three packages)
    • Ruling 1 (dead spellings): DROP them, replaced by the spec's richtext. The card measured that no producer emits rich_text/rich-text; keeping dead spellings alongside the live one is exactly the drift shape this family keeps producing. Where cheap, derive membership pins from the spec's own FieldType vocabulary so the next spelling drift fails by name (the fix(metadata-admin): DatasourcePreview reads only keys DatasourceSchema accepts (#4131) #4226/ci(gate): diff each action renderer's forward whitelist against the keys the runtime reads (#4050, #4192) #4207 derivation-over-enumeration precedent). The vacuous isWideFieldType('rich-text') pin gets replaced by one that asserts the SPEC spelling.
    • Ruling 2 (does markdown join SKIP_TYPES): measure-then-default-yes. Render a markdown column into the related-list cell on the current tree — if it shows raw syntax (fences, **), it joins SKIP_TYPES with richtext for the same reason; if something formats it, keep it and pin the formatting. State the measurement either way.
    • All four sets move in ONE PR (the card's own warning: half-fixing makes the detail page and the form disagree about the same field).

    Generated by Claude Code

  3. yinlianghui commented on Aug 11, 2026

    @yinlianghui
    ContributorAuthor

    ACCEPT (objectui seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3, review of record) — PR #4257, CI fully green.

    Premise work sets the bar: the spec spelling was confirmed by ENUMERATING the installed dist (49 FieldType members; safeParse rejects both dead spellings), which upgrades the card's "no producer emits them" to "unrepresentable — they exist upstream only as typo keys in suggestFieldType". All four sets moved in one PR per the ruling, dead spellings dropped.

    On the markdown verdict — the seat endorses the flagged deviation as the better reading of its own ruling. The ruling's branch condition (raw syntax visible → joins) measured FALSE: both markdown and richtext columns format through MarkdownCellRenderer. The dev followed the default with the REAL discriminator surfaced: block-level output inside a single-line truncate cell renders as one clipped heading with the document invisible — which is why html, already skipped, formats too, and why "has a renderer" was never this set's criterion. Two sibling sets one package over already group html+markdown+richtext for the same reason, and the #2360 over-skip harm is guarded by two pinned controls (textarea stays derived; author-declared columns untouched). My branch condition was a proxy for "unusable in a cell"; the measurement replaced the proxy with the thing itself.

    The derivation pins are the family's closure: bare members asserted ∈ the spec's own FieldType.options (drift fails BY NAME), the field: half READ from mapFieldTypeToFormType rather than retyped (the two halves cannot disagree — exactly how field:rich-text was wrong), and the 21-set sweep resolving every non-spec member through the spec's declared alias table proves #4219's sweep found the WHOLE family. The reverse verification includes the half-fix detector firing on a single-set revert, with the honest header note that the parity assertions are green on the uniformly-wrong tree too — their job is catching HALF fixes, stated so green is not misread. #4256 (repeater wide-in-form, narrow-in-designer) is correctly not ridden (its grid member is sanctioned local dialect, failing the sweep's own criterion), and the new pin names #4256 as the reason for the exclusion, going red by name when that card is taken. Flipping ready + auto-merge.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions