Skip to content

fix(editor): disable OS-level autocorrect causing aggressive "im" replacement (#14477)#14486

Open
YahyaJan280 wants to merge 1 commit intotoeverything:canaryfrom
YahyaJan280:bugfix/aggressive-text-replacement-14477
Open

fix(editor): disable OS-level autocorrect causing aggressive "im" replacement (#14477)#14486
YahyaJan280 wants to merge 1 commit intotoeverything:canaryfrom
YahyaJan280:bugfix/aggressive-text-replacement-14477

Conversation

@YahyaJan280
Copy link
Copy Markdown

@YahyaJan280 YahyaJan280 commented Feb 21, 2026

Closes #14477

Summary

Fixes an issue where typing words starting with "im" (e.g., "implicit", "image", "important") would automatically be replaced with "I'm".

Root Cause

On macOS, contenteditable elements without:

  • autocorrect="off"
  • autocapitalize="off"

allow OS-level text replacement.

macOS includes a built-in rule:
im → I'm

This incorrectly triggers even when "im" is part of a longer word.

Solution

Added:

  • autocorrect="off"
  • autocapitalize="off"

to relevant contenteditable elements to prevent OS-level interference.

Result

  • Normal words are no longer modified
  • Markdown shortcuts continue working
  • No regression observed

Summary by CodeRabbit

  • Bug Fixes
    • Disabled macOS autocorrect and automatic text replacement across multiple text editing components.
    • Affects rich text editors, edgeless text blocks, page blocks, and transcription features to prevent unwanted automatic modifications.
    • Improves editing experience by maintaining user control over text input without OS-level interference during content creation.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Feb 21, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 21, 2026

📝 Walkthrough

Walkthrough

The changes disable macOS text replacement across multiple editor components by setting autocorrect and autocapitalize HTML attributes to off in editable elements. This addresses aggressive autocorrection behavior that was replacing partial words (e.g., "implicit" becoming "I'mplicit").

Changes

Cohort / File(s) Summary
Core Editor Components
blocksuite/affine/blocks/edgeless-text/src/edgeless-text-block.ts, blocksuite/affine/blocks/root/src/page/page-root-block.ts, blocksuite/affine/rich-text/src/rich-text.ts
Added autocorrect="off" and autocapitalize="off" attributes to contenteditable elements to disable macOS text replacement during editing.
AI & Transcription
packages/frontend/core/src/blocksuite/ai/blocks/ai-chat-block/ai-transcription-block.ts
Added autocorrect="off" and autocapitalize="off" attributes immediately after enabling contentEditable to prevent text replacement in transcription mode.
Playground Examples
blocksuite/playground/examples/inline/test-page.ts
Added autocorrect="off" and autocapitalize="off" attributes during component initialization to suppress macOS text replacement behavior.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 When words got twisted by autocorrect's spell,
We set those attributes and all was well,
No more "I'm" replacing what shouldn't be,
Now typing flows naturally and free!
Off goes the replacement, both far and near,
The editor's working the way we all cheer. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: disabling OS-level autocorrect to prevent aggressive 'im' replacement on macOS.
Linked Issues check ✅ Passed The PR directly addresses issue #14477 by adding autocorrect='off' and autocapitalize='off' attributes to contenteditable elements across all affected files, preventing macOS text replacement.
Out of Scope Changes check ✅ Passed All changes are directly related to the stated objective: adding autocorrect and autocapitalize attributes to contenteditable elements to fix the 'im' replacement issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
blocksuite/affine/rich-text/src/rich-text.ts (1)

402-407: autocorrect/autocapitalize are static in the template regardless of readonly.

The other components in this PR (page-root-block.ts, edgeless-text-block.ts) remove these attributes when entering readonly mode. Here they're always present even when contenteditable="false". Functionally harmless, but the pattern is inconsistent across the PR.

♻️ Optional — align with the rest of the PR using Lit's `nothing`
-import { css, html, type TemplateResult } from 'lit';
+import { css, html, nothing, type TemplateResult } from 'lit';
    return html`<div
      contenteditable=${this.readonly ? 'false' : 'true'}
-     autocorrect="off"
-     autocapitalize="off"
+     autocorrect=${this.readonly ? nothing : 'off'}
+     autocapitalize=${this.readonly ? nothing : 'off'}
      class=${classes}
    ></div>`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@blocksuite/affine/rich-text/src/rich-text.ts` around lines 402 - 407, The
template in rich-text.ts currently renders autocorrect and autocapitalize
attributes unconditionally even when this.readonly is true; change the html
template that returns the contenteditable div to conditionally omit these
attributes when readonly by using Lit's nothing for autocorrect and
autocapitalize (e.g., bind autocorrect=${this.readonly ? nothing : 'off'} and
autocapitalize=${this.readonly ? nothing : 'off'}) so the div matches the
readonly behavior in page-root-block.ts and edgeless-text-block.ts; keep the
contenteditable and class=${classes} bindings as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@blocksuite/playground/examples/inline/test-page.ts`:
- Around line 128-136: The autocorrect/autocapitalize attributes are being set
on the outer TestRichText element instead of on the actual editable container,
so move the two setAttribute calls from firstUpdated to apply to the _container
element before calling this.inlineEditor.mount(this._container, this);
specifically, in the override firstUpdated() method set
_container.setAttribute('autocorrect','off') and
_container.setAttribute('autocapitalize','off') (or otherwise ensure the same
attributes are present on the element passed into InlineEditor.mount and not the
host element), then call this.inlineEditor.mount(this._container, this).

---

Nitpick comments:
In `@blocksuite/affine/rich-text/src/rich-text.ts`:
- Around line 402-407: The template in rich-text.ts currently renders
autocorrect and autocapitalize attributes unconditionally even when
this.readonly is true; change the html template that returns the contenteditable
div to conditionally omit these attributes when readonly by using Lit's nothing
for autocorrect and autocapitalize (e.g., bind autocorrect=${this.readonly ?
nothing : 'off'} and autocapitalize=${this.readonly ? nothing : 'off'}) so the
div matches the readonly behavior in page-root-block.ts and
edgeless-text-block.ts; keep the contenteditable and class=${classes} bindings
as-is.

Comment on lines 128 to 136
override firstUpdated() {
this.contentEditable = 'true';
this.style.outline = 'none';

// Disable macOS text replacement to prevent aggressive autocorrect
this.setAttribute('autocorrect', 'off');
this.setAttribute('autocapitalize', 'off');

this.inlineEditor.mount(this._container, this);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's find the InlineEditor implementation files
find . -type f -name "*.ts" -path "*inline*" | head -20

Repository: toeverything/AFFiNE

Length of output: 1100


🏁 Script executed:

# Search for the mount method definition in inline editor code
rg -n "mount\s*\(" blocksuite --type ts -A 5 | grep -A 5 "mount.*contentEditable\|contentEditable.*mount" | head -30

Repository: toeverything/AFFiNE

Length of output: 45


🏁 Script executed:

# Look for InlineEditor class and its mount method
ast-grep --pattern 'class InlineEditor {
  $$$
  mount($$$) {
    $$$
  }
  $$$
}'

Repository: toeverything/AFFiNE

Length of output: 45


🏁 Script executed:

# Search for contentEditable assignments in the inline editor implementation
rg -n "contentEditable\s*=" blocksuite --type ts -B 2 -A 2

Repository: toeverything/AFFiNE

Length of output: 8899


🏁 Script executed:

# Check the test-page.ts file structure
cat -n blocksuite/playground/examples/inline/test-page.ts | head -150

Repository: toeverything/AFFiNE

Length of output: 5105


🏁 Script executed:

# Find and examine the InlineEditor.mount() method
rg -n "mount\s*\(" blocksuite/framework/std/src/inline/inline-editor.ts -B 2 -A 15

Repository: toeverything/AFFiNE

Length of output: 1359


🏁 Script executed:

# Look at the full InlineEditor implementation for mount and how it handles rootElement
cat -n blocksuite/framework/std/src/inline/inline-editor.ts | sed -n '1,100p'

Repository: toeverything/AFFiNE

Length of output: 4688


🏁 Script executed:

# Search for where rootElement is assigned in mount
rg -n "rootElement\s*=" blocksuite/framework/std/src/inline/inline-editor.ts -B 3 -A 3

Repository: toeverything/AFFiNE

Length of output: 595


🏁 Script executed:

# Look at rich-text.ts to see the production pattern being referenced
find . -name "rich-text.ts" -type f | head -5

Repository: toeverything/AFFiNE

Length of output: 195


🏁 Script executed:

# Find and examine the rich-text.ts file mentioned in the review
rg -n "firstUpdated\|mount\|autocorrect" blocksuite --type ts | grep -i "rich-text" | head -10

Repository: toeverything/AFFiNE

Length of output: 45


🏁 Script executed:

# Search for rich-text implementation that has the correct pattern
fd "rich-text" --type f --extension ts | head -10

Repository: toeverything/AFFiNE

Length of output: 345


🏁 Script executed:

# Examine the production rich-text.ts implementation
cat -n blocksuite/affine/rich-text/src/rich-text.ts | head -200

Repository: toeverything/AFFiNE

Length of output: 6993


🏁 Script executed:

# Look for mount, firstUpdated, autocorrect in rich-text.ts
rg -n "mount\|firstUpdated\|autocorrect\|autocapitalize" blocksuite/affine/rich-text/src/rich-text.ts -B 2 -A 5

Repository: toeverything/AFFiNE

Length of output: 45


🏁 Script executed:

# Look at test-page.ts more carefully around the mount call and _container definition
cat -n blocksuite/playground/examples/inline/test-page.ts | sed -n '120,160p'

Repository: toeverything/AFFiNE

Length of output: 1544


🏁 Script executed:

# Look for where rich-text.ts mounts the editor and handles attributes
rg -n "mount\|firstUpdated\|connectedCallback" blocksuite/affine/rich-text/src/rich-text.ts -B 3 -A 8

Repository: toeverything/AFFiNE

Length of output: 45


🏁 Script executed:

# Check the render method in rich-text.ts to see the template structure
cat -n blocksuite/affine/rich-text/src/rich-text.ts | sed -n '230,350p'

Repository: toeverything/AFFiNE

Length of output: 4532


🏁 Script executed:

# Look for the _inlineEditorContainer query and template in rich-text.ts
rg -n "_inlineEditorContainer\|inline-editor\|@query" blocksuite/affine/rich-text/src/rich-text.ts -B 2 -A 3

Repository: toeverything/AFFiNE

Length of output: 45


🏁 Script executed:

# Look for the render method in rich-text.ts
cat -n blocksuite/affine/rich-text/src/rich-text.ts | sed -n '370,450p'

Repository: toeverything/AFFiNE

Length of output: 3012


🏁 Script executed:

# Search for where inlineEditorContainer is defined (property or query)
rg -n "inlineEditorContainer\|@query\|query(" blocksuite/affine/rich-text/src/rich-text.ts -B 1 -A 2

Repository: toeverything/AFFiNE

Length of output: 187


🏁 Script executed:

# Look at the full render template in rich-text.ts
cat -n blocksuite/affine/rich-text/src/rich-text.ts | sed -n '375,425p'

Repository: toeverything/AFFiNE

Length of output: 1758


🏁 Script executed:

# Search for where attributes like autocorrect are set in rich-text.ts
rg -n "autocorrect\|autocapitalize\|setAttribute" blocksuite/affine/rich-text/src/rich-text.ts -B 2 -A 2

Repository: toeverything/AFFiNE

Length of output: 45


🏁 Script executed:

# Look at test-page render to understand _container
cat -n blocksuite/playground/examples/inline/test-page.ts | sed -n '155,200p'

Repository: toeverything/AFFiNE

Length of output: 1620


🏁 Script executed:

# Look for the _container definition and how it's queried in test-page.ts
rg -n "_container\|@query" blocksuite/playground/examples/inline/test-page.ts -B 1 -A 2

Repository: toeverything/AFFiNE

Length of output: 45


🏁 Script executed:

# Verify that mount() calls setReadonly which sets contentEditable
cat -n blocksuite/framework/std/src/inline/inline-editor.ts | sed -n '230,280p'

Repository: toeverything/AFFiNE

Length of output: 1704


🏁 Script executed:

# Search for `@query` decorator defining _container in test-page.ts
cat -n blocksuite/playground/examples/inline/test-page.ts | grep -n "query\|_container"

Repository: toeverything/AFFiNE

Length of output: 1002


🏁 Script executed:

# Check the entire TestRichText class definition
cat -n blocksuite/playground/examples/inline/test-page.ts | sed -n '126,250p'

Repository: toeverything/AFFiNE

Length of output: 4567


Set autocorrect and autocapitalize attributes on the _container element passed to mount(), not on the outer element.

The attributes are currently set on the outer TestRichText element, but InlineEditor.mount() calls setReadonly(), which explicitly sets contentEditable on _container (line 276 of inline-editor.ts). This makes _container an independent contenteditable element that defaults to autocorrect=on and autocapitalize=on per-element, bypassing the settings on the ancestor element.

The production component (rich-text.ts) correctly places these attributes directly on the <div> that is passed to inlineEditor.mount(). Align this playground code with that approach:

✅ Set attributes on _container
  override firstUpdated() {
    this.contentEditable = 'true';
    this.style.outline = 'none';
-
-   // Disable macOS text replacement to prevent aggressive autocorrect
-   this.setAttribute('autocorrect', 'off');
-   this.setAttribute('autocapitalize', 'off');

    this.inlineEditor.mount(this._container, this);
+   // Disable macOS text replacement to prevent aggressive autocorrect
+   this._container.setAttribute('autocorrect', 'off');
+   this._container.setAttribute('autocapitalize', 'off');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
override firstUpdated() {
this.contentEditable = 'true';
this.style.outline = 'none';
// Disable macOS text replacement to prevent aggressive autocorrect
this.setAttribute('autocorrect', 'off');
this.setAttribute('autocapitalize', 'off');
this.inlineEditor.mount(this._container, this);
override firstUpdated() {
this.contentEditable = 'true';
this.style.outline = 'none';
this.inlineEditor.mount(this._container, this);
// Disable macOS text replacement to prevent aggressive autocorrect
this._container.setAttribute('autocorrect', 'off');
this._container.setAttribute('autocapitalize', 'off');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@blocksuite/playground/examples/inline/test-page.ts` around lines 128 - 136,
The autocorrect/autocapitalize attributes are being set on the outer
TestRichText element instead of on the actual editable container, so move the
two setAttribute calls from firstUpdated to apply to the _container element
before calling this.inlineEditor.mount(this._container, this); specifically, in
the override firstUpdated() method set
_container.setAttribute('autocorrect','off') and
_container.setAttribute('autocapitalize','off') (or otherwise ensure the same
attributes are present on the element passed into InlineEditor.mount and not the
host element), then call this.inlineEditor.mount(this._container, this).

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

Labels

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[Bug]: Aggressive text replacement.

2 participants