Skip to content

fix(ace-editor): caret drift from late font loads and emoji widths#41697

Merged
rusackas merged 9 commits into
apache:masterfrom
rusackas:fix/ace-cursor-remeasure-fonts
Jul 6, 2026
Merged

fix(ace-editor): caret drift from late font loads and emoji widths#41697
rusackas merged 9 commits into
apache:masterfrom
rusackas:fix/ace-cursor-remeasure-fonts

Conversation

@rusackas

@rusackas rusackas commented Jul 2, 2026

Copy link
Copy Markdown
Member

SUMMARY

Fixes the residual caret/cursor misalignment when editing any Ace-backed text area (Markdown, SQL Lab, Explore custom SQL, CSS, etc.) reported in #41664.

Why the existing fix doesn't cover it: the font-family fix from #38928 (adding font-family to .ace_content / .ace_text-layer) is already present on master and is applied consistently — setOptions={{ fontFamily }} and the CSS both use the same editorFontFamily. So there is no font-family mismatch left. The remaining drift is a measurement-timing problem:

Ace measures glyph width once, at editor construction, via its internal FontMetrics. If the editor font finishes loading after that measurement, the cached character width no longer matches the rendered glyphs, and the caret drifts further from the text the longer the line — which matches the screenshot in the issue (misalignment grows with column position).

Fix: wrap the editor's onLoad to force Ace to re-measure — immediately on load and again once document.fonts.ready resolves — via renderer.updateFontSize() (which calls through to FontMetrics.checkForSizeChanges()) + renderer.onResize(true). A consumer-supplied onLoad is still invoked. No CSS or font changes.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A (caret positioning; see the screencast in #41664). Needs a visual confirm on the repro — I don't have a way to verify the rendered caret headlessly.

TESTING INSTRUCTIONS

cd superset-frontend
npx jest packages/superset-ui-core/src/components/AsyncAceEditor/AsyncAceEditor.test.tsx --no-coverage

New unit test asserts the editor re-measures after document.fonts.ready and that a consumer-provided onLoad is still called. Manual: open a Markdown/text control in a dashboard (or SQL Lab) and type a long line — the caret should stay aligned with the glyphs.

ADDITIONAL INFORMATION

Note: the earlier #38928 change is already merged and does not resolve this; re-applying it (as suggested on the issue) would be a no-op. This targets the measurement-timing root cause instead.

…t drift

Ace caches glyph width once at editor construction via its internal
FontMetrics. If the editor font settles after that measurement, the
cached width no longer matches the rendered glyphs and the caret drifts
further from the text the longer the line — the residual misalignment in
issue apache#41664 that the font-family CSS from apache#38928 does not address (the
font family is already applied consistently via setOptions and CSS; the
problem is measurement timing, not the font itself).

Force Ace to re-measure on load and again once document.fonts.ready
resolves, so the caret realigns. A consumer-supplied onLoad is still
invoked.

Fixes apache#41664
@bito-code-review

bito-code-review Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #d91e17

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: 540064c..540064c
    • superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/AsyncAceEditor.test.tsx
    • superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/index.tsx
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@netlify

netlify Bot commented Jul 2, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 80fdd5e
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a4bede0936c8500093fc90b
😎 Deploy Preview https://deploy-preview-41697--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.24242% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 64.75%. Comparing base (06f421e) to head (65bb7fb).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...et-ui-core/src/components/AsyncAceEditor/index.tsx 95.45% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #41697      +/-   ##
==========================================
+ Coverage   64.73%   64.75%   +0.01%     
==========================================
  Files        2687     2687              
  Lines      148743   148502     -241     
  Branches    34329    34277      -52     
==========================================
- Hits        96295    96164     -131     
+ Misses      50681    50599      -82     
+ Partials     1767     1739      -28     
Flag Coverage Δ
javascript 69.63% <99.24%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…types

updateFontSize() runs Ace's checkForSizeChanges, which already triggers
a forced resize and full re-render when the measured character size
changed, so the explicit renderer.onResize(true) was redundant — and it
broke tsc since onResize is not part of ace-builds' public
VirtualRenderer type.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rusackas rusackas requested review from EnxDev, geido and sadpandajoe July 3, 2026 00:57
@bito-code-review

bito-code-review Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #478bde

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: 540064c..6b7dca5
    • superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/AsyncAceEditor.test.tsx
    • superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/index.tsx
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@EnxDev

EnxDev commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

EnxDev's Review Agent — #41697 · HEAD 6b7dca5

comment — the change is safe and CI-green, but the vendored Ace source contradicts its root-cause premise: this very likely does not fix #41664, and Fixes #41664 will auto-close the issue on merge.

🔴 Functional

  • superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/index.tsx:296 · Medium — The re-measure is very likely a no-op against the repro. ace-builds ^1.41.0 (1.44.0 installed) already re-measures continuously: FontMetrics attaches a ResizeObserver to its measure node (USE_OBSERVER = typeof ResizeObserver == "function"$addObservercheckForSizeChanges), with ~500 ms idle polling as the no-ResizeObserver fallback — so a late font load self-corrects today on every modern browser, without this patch. A drift that persists (the screencast in Cursor still misaligned when editing any TextArea/Markdown #41664) implies the measure node stably renders different metrics than the text layer — plausible since the fix(ace-editor): fix cursor misalignment in markdown editor #38928 !important font-family targets .ace_content/.ace_text-layer while the measure node is a direct child of .ace_editor and uses the container font; in that state checkForSizeChanges() sees "no change" and updateFontSize() does nothing. Impact: merging auto-closes the issue while users still see the drift. Could we get the visual confirm on the repro (as the PR body already requests) before merge — and if it doesn't reproduce-fix, compare computed font-family/letter-spacing of the FontMetrics measure node vs .ace_text-layer on the repro instead? regression test: not possible headlessly — gate the merge on the manual repro check.

🟡 Should-fix

  • superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/index.tsx:285 — the comment "Ace measures glyph width once, when the editor is constructed" is contradicted by the vendored source (ResizeObserver + idle polling re-measure continuously) → correct it so the next reader doesn't inherit a wrong model; same claim in the PR body.

🔵 Nits

  • superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/index.tsx:302 — the post-fonts.ready remeasure can fire after unmount on a destroyed editor; harmless today only because .catch(() => {}) swallows the throw — a disposal guard would make it explicit.
  • superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/AsyncAceEditor.test.tsx:87 — the spy is installed inside the consumer onLoad, so it can only observe the post-fonts.ready call; spying on VirtualRenderer.prototype.updateFontSize before render would also cover the immediate re-measure.

🙌 Praise

  • AsyncAceEditor.test.tsx:76 — careful document.fonts descriptor save/restore, and destructuring onLoad out of props so {...props} can't clobber the wrapper is exactly right.

Reviewed by EnxDev's Review Agent — @EnxDev · HEAD 6b7dca5.

Ace's FontMetrics re-measures via a ResizeObserver on its measure node,
not only once at construction; reword the comment to match the vendored
ace-builds behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rusackas

rusackas commented Jul 5, 2026

Copy link
Copy Markdown
Member Author

Good catch on the FontMetrics point, @EnxDev, the ResizeObserver re-measure does contradict the "measures once" wording, so I reworded that comment. You're right that this shouldn't merge (and auto-close #41664) until I've confirmed the caret actually lands on the repro, which I can't do headlessly. Holding on that.

@bito-code-review

Copy link
Copy Markdown
Contributor

The flagged issue is correct. In environments where document.fonts or document.fonts.ready is undefined, the optional chaining document.fonts?.ready?.then(...) evaluates to undefined, causing the subsequent .catch call to throw a TypeError.

To resolve this, you can guard the entire promise chain by checking for the existence of document.fonts?.ready before chaining, or by using a safer approach to handle the promise.

Proposed Fix

            remeasure();
            if (document.fonts?.ready) {
              document.fonts.ready.then(remeasure).catch(() => {});
            }
            onLoad?.(editor);

This change ensures that the .catch method is only accessed if document.fonts.ready exists, preventing the runtime error in environments without the Font Loading API.

Would you like me to check the rest of the comments on this PR and implement fixes for them as well?

superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/index.tsx

remeasure();
            if (document.fonts?.ready) {
              document.fonts.ready.then(remeasure).catch(() => {});
            }
            onLoad?.(editor);

@bito-code-review

bito-code-review Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #ab91f3

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: 6b7dca5..582c21a
    • superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/index.tsx
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@rusackas rusackas requested a review from hainenber July 6, 2026 02:15
@TasmiaZerin1128

Copy link
Copy Markdown

It's broken again. Added a screen recording to understand the issue.

superset-cursor.mp4

@rusackas rusackas added the 🎪 ⚡ showtime-trigger-start Create new ephemeral environment for this PR label Jul 6, 2026
@github-actions github-actions Bot added 🎪 582c21a 🚦 building 🎪 ⌛ 48h Environment expires after 48 hours (default) and removed 🎪 ⚡ showtime-trigger-start Create new ephemeral environment for this PR labels Jul 6, 2026
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🎪 Showtime is building environment on GHA for 582c21a

…onts.ready

fonts.ready resolves once, and can settle before a lazily-referenced
editor font even starts loading; waiting on it alone misses the swap
entirely, which is the residual drift reported on the PR. Explicitly
request the editor font via fonts.load() and re-measure when that load
resolves, keep the fonts.ready path for loads already in flight, and
re-measure on subsequent loadingdone events while mounted (unsubscribed
on unmount).

Also guards the promise chains with full optional chaining: the
previous expression threw at .catch when document.fonts was
unavailable (CodeAnt catch), and the re-measure itself now tolerates a
font event firing after the editor was destroyed.

Adds tests for the no-Font-Loading-API case, the fonts.load()
re-measure with an already-settled fonts.ready (the reported hole), and
the loadingdone subscribe/unsubscribe lifecycle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pull-request-size pull-request-size Bot removed the size/M label Jul 6, 2026
Ace forces "full width" chars into 2×charWidth boxes and counts them as
two columns, so grid math and pixels agree; its tables only cover
East-Asian ranges. Astral emoji accidentally work (the renderer's
surrogate-pair branch + two-code-unit counting), but BMP emoji with
default emoji presentation (✨ U+2728, ⭐ …) count as one column while
rendering ~1.6 cells, drifting the caret on any line containing one —
the residual misalignment in apache#41664 after the font-metrics fix.
Upstream ace still lacks this (ajaxorg/ace#3404).

Extend the same mechanism to emoji: override the two EditSession width
methods (verbatim internals plus an emoji branch; the originals close
over a module-private isFullWidth) and wrap the text layer's
$renderToken to emit the forced-width .ace_cjk box for emoji clusters,
delegating everything else to the original. VS16 sequences top up to
two columns total; ZWJ/flag sequences are out of scope (equally
misaligned before).

Tests cover classification, VS16 accounting, caret math on the reported
repro line, astral parity, renderer box geometry, the emoji-free
fast path, and patch idempotence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pull-request-size pull-request-size Bot added size/XL and removed size/L labels Jul 6, 2026
@rusackas rusackas changed the title fix(ace-editor): re-measure font metrics after fonts load to fix caret drift fix(ace-editor): caret drift from late font loads and emoji widths Jul 6, 2026
@rusackas

rusackas commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

Second root cause found and fixed in 897d2f9, thanks to the ✨ screenshot: Ace's full-width tables only cover East-Asian ranges. Astral emoji (💡, surrogate pairs) accidentally align because the renderer's surrogate branch forces a 2-cell box and the column counter counts two code units, but BMP emoji like ✨ (U+2728) fall through both nets... one column in the math, ~1.6 cells on screen, and the caret drifts on exactly those lines. Upstream ace has this open as ajaxorg/ace#3404.

The fix extends the mechanism ace already uses for CJK to emoji-presentation codepoints and VS16 sequences: two columns in the session math, and the same forced 2×charWidth .ace_cjk box in the text layer, so grid and pixels agree. ZWJ families and flag pairs are left out (they need grapheme segmentation across every layer, and were equally misaligned before).

Eight new tests, including caret math on the literal ### ✨Header 3 repro line. Title updated to match the wider scope.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🎪 Showtime is building environment on GHA for 897d2f9

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🎪 Showtime deployed environment on GHA for 897d2f9

Environment: http://54.245.181.40:8080 (admin/admin)
Lifetime: 48h auto-cleanup
Updates: New commits create fresh environments automatically

aminghadersohi and others added 2 commits July 6, 2026 10:59
…okens

The merge with master resolves the pre-commit failure (CI formats the
merge result, and master's lodash-es bump had touched this file).

Adds parity tests for the non-emoji branches of the copied EditSession
internals (tabs, early return, break, space/punctuation/CJK tokens) to
restore the core-packages-ts project coverage, which caught a real gap:
VS16 after a text-presentation base (❤️) fell through to a plain CHAR,
leaving the pair splittable at wrap. It now pushes CHAR_EXT.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🎪 Showtime is building environment on GHA for 80fdd5e

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🎪 Showtime deployed environment on GHA for 80fdd5e

Environment: http://54.191.140.2:8080 (admin/admin)
Lifetime: 48h auto-cleanup
Updates: New commits create fresh environments automatically

master bumped prettier to 3.9.3, which wraps one pre-existing template
interpolation differently; CI formats the merge result so the file must
match the new version. Also brings emojiWidthPatch.ts to 100% coverage
(the core-packages-ts codecov flag targets 100%): the unreachable
match fallback is gone in favor of matchAll, and tests now cover the
leading-emoji split part and the memoized emoji-base lookup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🎪 Showtime is building environment on GHA for 65bb7fb

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🎪 Showtime deployed environment on GHA for 65bb7fb

Environment: http://34.222.98.66:8080 (admin/admin)
Lifetime: 48h auto-cleanup
Updates: New commits create fresh environments automatically

@rusackas

rusackas commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

I think it's actually fixed now!

image

Merging, but happy to make touchups or even rever this if needed... onward and upward!

@bito-code-review

Copy link
Copy Markdown
Contributor

Bito Automatic Review Skipped – PR Already Merged

Bito scheduled an automatic review for this pull request, but the review was skipped because this PR was merged before the review could be run.
No action is needed if you didn't intend to review it. To get a review, you can type /review in a comment and save it

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cursor still misaligned when editing any TextArea/Markdown

6 participants