Skip to content

fix: follow each word's own direction, compute word facts from the whole wordset (@miodec) - #8557

Merged
Miodec merged 8 commits into
masterfrom
refactor/ep3-per-word-facts
Nov 9, 2026
Merged

Miodec merged 8 commits into
masterfrom
refactor/ep3-per-word-facts

Conversation

@Miodec

@Miodec Miodec commented Nov 7, 2026

Copy link
Copy Markdown
Member

Roadmap step EP3. Redoes #7787 on top of the render store, reusing its RTL character table.

Summary

Each word resolves its direction once, from its first strong character. Rendering and the caret read that value instead of checking per letter.

 TestWords.push(word)
+  word.direction = first strong character, else the test's direction

 <Word>
-  classList wordRtl (only when the test marks RTL words)
+  dir={word.direction}, unicode-bidi: bidi-override

 caret.goTo
-  isWordRightToLeft(word, per letter)
+  testWord.direction ?? zen word's last typed character ?? test direction
   tape offset: still from the test direction

Numbers, newlines, tabs and Korean status stay test-level, but are scanned from the generated words plus the wordset, because words stream in during a test. koreanStatus stays one boolean in the EventLog context, so no version bump.

Evidence

Worst caret distance from the next letter's nearest edge, in px, same script on both commits:

Word that differs from the test Tape Before (0e07023d4) After
Hebrew/Arabic in an English test letter 723–736 79–89
Hebrew/Arabic in an English test word 621–634 85–89
English in a Hebrew test letter 714 105
English in a Hebrew test word 606 105
Either off 2–3 2–3

With tape off, __mt.caretOffset() on those words goes from 26–30px (caret on the wrong side of the letter) to 2–3px.

Unit tests: new words-generator.spec.ts covers facts found only in the wordset. test-words, strings, Words and test-ui-words specs are updated for per-word direction. Lint and type check pass.

Browser checks on the branch: Arabic and Hebrew in all tape modes, polyglot, backwards, joining scripts, facts that only appear late in the wordset, streaming, no visible flip on restart, and an 18ms scan on a 450k word list.

Known gaps:

  • With tape on, the caret is still off its letter in the mixed-direction cases above. Up to ~194px was seen with other text. This is already there before this PR and is recorded under T3.
  • Not yet compared with master: in zen, RTL input in an LTR test leaves the caret one letter off; with backwards, the caret is ~1,069px off after the first restart, until typing or the next restart.
  • مرحبا123 shows its digits as 321.

Deviations from the plan: dir plus a Tailwind class rather than .ltr/.rtl; \p{N} rather than a numeral table.

Merge Danger

Door: two-way

Only rendering and caret code changes, with no stored data or EventLog version change.

Blast Radius: test-page

Direction of every word and caret placement on the test page, most visibly RTL languages, polyglot, custom text and the backwards funbox. .wordRtl is removed from test.scss, so custom user styles targeting it stop matching.

🤖 Generated with Claude Code

Co-authored-by: Nad Alaba 37968805+nadalaba@users.noreply.github.com

Miodec and others added 4 commits November 7, 2026 18:42
Numbers and Korean status were read from the first batch of words only, so a
word arriving later in a long test didn't count. Tab, newline, numbers and
Korean are now computed together in the generator, over the generated words and
the wordset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds getWordDirection and reverseDirection, using Unicode's strong right-to-left
bidi classes. Nothing uses them yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each test word carries a resolved direction, passed on to the render store.
Every word lays out its letters in that direction, replacing wordRtl and
marksRtlWords, and the caret reads it instead of checking per letter. Zen words
have no direction: they inherit the test's, the caret uses the last typed
character. The tape stays anchored by the test's direction.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 7, 2026 17:58
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@monkeytypegeorge monkeytypegeorge added frontend User interface or web stuff docs Related to Markdown files and documentation assets Languages, themes, layouts, etc. labels Nov 7, 2026
@github-actions github-actions Bot added the waiting for review Pull requests that require a review before continuing label Nov 7, 2026

Copilot AI left a comment

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.

Note

Copilot was unable to complete this review at the selected tier due to model policy or plan restrictions.

🟡 Changes recommended

A moderate active issue remains where getTestWordFacts needlessly allocates a giant joined string and its ASCII-only digit check can miss non-ASCII numerals, potentially leaving wordsHaveNumbers unset and the keymap top row hidden.

1 open issue
What changed in this PR

This PR implements EP3 changes to resolve direction per word (from the first strong character) and then uses that resolved value throughout rendering and caret logic, while also computing test-level “word facts” (numbers/newlines/tabs/Korean status) from generated words plus the full wordset to stay correct during streaming.

Changes:

  • Resolve and persist direction on each test word; propagate through the render store and UI.
  • Render words using per-word dir plus unicode-bidi handling; remove the .wordRtl styling pathway.
  • Update caret/pace-caret to consume testDirection + per-word direction rather than per-letter RTL checks.
  • Add/update unit tests and update docs/specs for the new per-word direction and word-facts behavior.

Key review notes (unresolved):

  • unicode-bidi: bidi-override applied at the whole-word level can reverse digit sequences inside RTL words (e.g., "مرحبا123" displaying as "321"); consider unicode-bidi: isolate or conditional override use. (moderate)
  • getTestWordFacts currently flattens the entire wordset into one string (extra allocation) and hasNumbers only checks ASCII \d, missing non-ASCII numerals; scan per-word with early exits and use /\p{N}/u. (moderate)
File Notes
frontend/​static/​funbox/​backwards.css Removes/adjusts bidi-related styling now handled per word.
frontend/​src/​ts/​utils/​strings.ts Refactors direction utilities (e.g., getWordDirection / reversal).
frontend/​src/​ts/​utils/​direction-regex.ts Introduces shared “strong RTL character” regex/table.
frontend/​src/​ts/​test/​words-generator.ts Adds getTestWordFacts; moderate concern on full-wordset flattening and numeral detection.
frontend/​src/​ts/​test/​test-words.ts Stores resolved per-word direction on test words.
frontend/​src/​ts/​test/​test-ui.ts Updates restart/render integration to pass word objects (not just strings).
frontend/​src/​ts/​test/​test-logic.ts Wires new direction/facts flow through test setup/streaming.
frontend/​src/​ts/​test/​pace-caret.ts Updates caret API usage to rely on testDirection.
frontend/​src/​ts/​test/​caret.ts Updates caret targeting to use per-word direction + testDirection.
frontend/​src/​ts/​states/​words-render.ts Render-store buffering updated for word objects incl. optional direction.
frontend/​src/​ts/​states/​test.ts Switches to explicit test direction helper (removes per-letter check path).
frontend/​src/​ts/​elements/​caret.ts Caret positioning updated to use test/word direction inputs.
frontend/​src/​ts/​components/​pages/​test/​Word.tsx Adds per-word dir + bidi handling; moderate concern re: digit reversal with bidi-override.
frontend/​src/​styles/​test.scss Removes .wordRtl styling (behavior moved to per-word rendering).
frontend/​__tests__/​utils/​strings.spec.ts Updates tests for new direction utilities/semantics.
frontend/​__tests__/​test/​words-generator.spec.ts Adds coverage for word-facts computed from wordset + generated words.
frontend/​__tests__/​test/​test-words.spec.ts Updates tests for per-word direction resolution behavior.
frontend/​__tests__/​test/​test-ui-words.spec.tsx Updates UI word tests for object-based words/direction.
frontend/​__tests__/​components/​pages/​test/​Words.spec.tsx Updates component tests for new word shape/render behavior.
frontend/​__tests__/​components/​pages/​test/​TypoHints.spec.tsx Updates tests/helpers for new word object shape.
docs/​specs/​solid-monkeytype/​spec.md Documentation updated to reflect per-word direction + word-facts scan.
docs/​specs/​solid-monkeytype/​roadmap.md Roadmap updated to mark EP3 progress/completion notes.

Review effort: Balanced

Comment thread frontend/src/ts/test/words-generator.ts Resolved
Miodec and others added 2 commits November 7, 2026 19:26
Numbers mode converts digits to Arabic, Nepali, Bangla and Hindi numerals, which \d misses, so the keymap's top row could stay hidden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fehmer
fehmer previously approved these changes Nov 9, 2026
@github-actions github-actions Bot removed the waiting for review Pull requests that require a review before continuing label Nov 9, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Miodec
Miodec merged commit 50ce24a into master Nov 9, 2026
15 checks passed
@Miodec
Miodec deleted the refactor/ep3-per-word-facts branch November 9, 2026 13:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

assets Languages, themes, layouts, etc. docs Related to Markdown files and documentation frontend User interface or web stuff

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants