Skip to content

admin-rte: Enable TypeScript strict mode - #6260

Draft
VPS-thodax wants to merge 4 commits into
mainfrom
claude/com-1079-strict-admin-rte
Draft

admin-rte: Enable TypeScript strict mode#6260
VPS-thodax wants to merge 4 commits into
mainfrom
claude/com-1079-strict-admin-rte

Conversation

@VPS-thodax

@VPS-thodax VPS-thodax commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Status quo

Strict mode is not enabled everywhere in this repo. packages/admin/tsconfig.base.json stops at noImplicitAny and strictNullChecks, so @dextinity/admin-rte runs without the remaining strict checks.

Change

Enable strict for the package. All four resulting errors come from strictFunctionTypes, which compares callback parameters contravariantly:

  • ControlButton attaches onButtonClick to a button element, so the prop is typed with React's MouseEvent for HTMLButtonElement instead of the wider one for Element.
  • useInlineStyleType annotated its click handler with the DOM's global MouseEvent instead of React's, so it did not match IFeatureConfig.
  • createComponentSlot drops the generic of MUI's Select, which types the slot's onChange with an unknown value. useBlockTypes follows and narrows the block type where it is read.
  • requiredValidator is no longer declared as a FieldValidator: its optional meta parameter makes the type invariant in the field value, so it could not be passed to a string-valued Field.

Verification

Compared the emitted .d.ts files against main. Three types change, none of them breaking: the two in the changeset sit in a contravariant position, so handlers and validators that type-checked before still do, and BlockChangeEvent is not reachable through anything exported.

@dextinity/admin, @dextinity/cms-admin, the demo admin and Storybook typecheck against the rebuilt package.

Further information

@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Comment @coderabbitai help to get the list of available commands.

Comment thread packages/admin/admin-rte/src/core/Controls/useBlockTypes.tsx Outdated
Comment thread packages/admin/admin-rte/src/core/Controls/useBlockTypes.tsx Outdated
Comment on lines +7 to +9
// Not typed as `FieldValidator<...>`: its optional `meta` parameter makes the type invariant in the field
// value, which keeps the validator from being used for a `string`-valued field.
export const requiredValidator = (value: string | RawDraftContentState | undefined): ReactNode => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to keep using the FinalForm typing? Keeping the final form types seems more correct

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You were right, and it is now a FieldValidator again — 207fee8.

FieldValidator is invariant in the field value: FieldState both accepts it (change: (value: FieldValue | undefined) => void) and returns it, so FieldValidator<string | RawDraftContentState | undefined> is not assignable to FieldValidator<string>, which is what createRteField needs. A single instantiation therefore cannot work — but a generic one can:

export const requiredValidator = <T extends string | RawDraftContentState | undefined>(
    ...[value]: Parameters<FieldValidator<T>>
): ReactNode => { ... };

I verified the four candidates by compiling assignments rather than reasoning about it: the wide instantiation fails for FieldValidator<string>; an intersection of instantiations fails for a raw-state field; the generic satisfies both FieldValidator<string> and FieldValidator<RawDraftContentState>. It is also less restrictive than before for consumers, since it now fits either field value.


Generated by Claude Code

Strict mode is enabled in the starter, so the packages should follow.

All four errors come from strictFunctionTypes, which compares callback
parameters contravariantly:

- ControlButton attaches onButtonClick to a button element, so the prop
  is typed with MouseEvent<HTMLButtonElement> instead of the wider
  MouseEvent<Element>.
- useInlineStyleType annotated its click handler with the DOM's global
  MouseEvent rather than React's, so it did not match IFeatureConfig.
- createComponentSlot drops the generic of MUI's Select, which types the
  slot's onChange with an unknown value. useBlockTypes follows and
  narrows the block type where it is read.
- requiredValidator is no longer declared as FieldValidator: its optional
  meta parameter makes the type invariant in the field value, so the
  validator could not be passed to a string-valued Field.
@VPS-thodax
VPS-thodax force-pushed the claude/com-1079-strict-admin-rte branch from 95d86f8 to eda0763 Compare August 31, 2026 12:34
claude added 3 commits August 31, 2026 13:25
Keep requiredValidator a FieldValidator by making it generic over the
field value, instead of dropping the final-form type. FieldValidator is
invariant in the field value because FieldState both accepts and returns
it, so a single wide instantiation could not be passed to a string-valued
Field; a generic can.

Explain in useBlockTypes why the Select slot's event cannot stay typed
with DraftBlockType, and why the value has to be narrowed before use.
The reasoning belongs in the review conversation, not in the code.

Copy link
Copy Markdown
Contributor Author

Notes on the three non-obvious changes

Why the Select event cannot stay SelectChangeEvent<DraftBlockType>

The Select in BlockTypesControls is not MUI's Select directly — it is built with createComponentSlot(MuiSelect), which does not forward MUI's generic. The slot therefore types onChange with an unknown value. Under strictFunctionTypes callback parameters are compared contravariantly, so a handler declaring a narrower value is not assignable to it. The event has to be typed the way the slot passes it.

Why the value is narrowed in useBlockTypes

DraftBlockType is a string, so the unknown value has to be narrowed before it can be used as one. DraftBlockType is not gone from the file — it still types the block type everywhere else; it just cannot type the event. Every block type MenuItem is keyed by its block type, so a non-string cannot occur in practice, and if it did it falls into the same branch as the empty selection. Behaviour is unchanged.

requiredValidator keeps the final-form typing

Per the review, it is a FieldValidator again, now generic over the field value:

export const requiredValidator = <T extends string | RawDraftContentState | undefined>(
    ...[value]: Parameters<FieldValidator<T>>
): ReactNode => { ... };

FieldValidator is invariant in the field value, because FieldState both accepts it (change: (value: FieldValue | undefined) => void) and returns it. A single instantiation therefore cannot serve both a string-valued Field (what createRteField needs) and a raw-state one. I compiled the candidates instead of reasoning about them:

Form FieldValidator<string> FieldValidator<RawDraftContentState>
single wide instantiation
intersection of instantiations
generic

So the generic is the only form that keeps the final-form type and works for either field value — and it is less restrictive for consumers than what is on main today.


Generated by Claude Code

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants