Repository navigation
fix: follow each word's own direction, compute word facts from the whole wordset (@miodec) - #8557
Conversation
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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
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
directionon each test word; propagate through the render store and UI. - Render words using per-word
dirplusunicode-bidihandling; remove the.wordRtlstyling 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-overrideapplied at the whole-word level can reverse digit sequences inside RTL words (e.g.,"مرحبا123"displaying as"321"); considerunicode-bidi: isolateor conditional override use. (moderate)getTestWordFactscurrently flattens the entire wordset into one string (extra allocation) andhasNumbersonly 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
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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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.
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.
koreanStatusstays 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:
0e07023d4)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.tscovers facts found only in the wordset.test-words,strings,Wordsandtest-ui-wordsspecs 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:
backwards, the caret is ~1,069px off after the first restart, until typing or the next restart.مرحبا123shows its digits as321.Deviations from the plan:
dirplus 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
backwardsfunbox..wordRtlis removed fromtest.scss, so custom user styles targeting it stop matching.🤖 Generated with Claude Code
Co-authored-by: Nad Alaba 37968805+nadalaba@users.noreply.github.com