Repository navigation
refactor: Words triggers its own layout (flushes, config, font, resize) (@miodec) - #8507
Conversation
Words renders #words and owns its layout (createWordsLayout). test-ui drives it through intents (layout, relayout, resized, reset...) instead of querying #words. Logic moved unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dren LineBreak registers its elements by word index. Line jump and tape scroll walk the rendered words and line breaks from the render store, no longer depending on LineBreak's markup or class names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The out of focus warning transitioned every #words property, the inherited font size included, so layout measured the old size and clipped the words (eg. font size changed from the commandline). Only transition what .blurred changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
Letter widths change once the new font is applied and loaded, but nothing repositioned the caret until the next keystroke. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
- getLineAboveTop reads words from the render store, no sibling walk - font caret update skips reset words and finished tests - Words' layout prop renamed ref -> onLayout (it isn't an element) - stale comments about TestPage/test-ui owning #words updated - specs share renderWords and the static layouts mock from __harness__ Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
# Conflicts: # docs/AGENT_BROWSER.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
words-render hands its layout hook what the flush changed: new words (after a reset), the word change, the updated words. Words registers the hook and lays out new words from scratch, so test-ui no longer tracks pending layout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
resetWordsRender bumps a generation; Words resets on it, and again on the new words' flush in case a batch deferred the effect past it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
|
Continuous integration check(s) failed. Please review the failing check's logs and make the necessary changes. |
There was a problem hiding this comment.
🔵 Needs a closer look
It relocates the app's most intricate layout/animation logic (line jump, tape scroll, caret sync) across module boundaries and bundles at least one new runtime behavior, which warrants human verification of visual/timing equivalence.
No open issues 🎉
What changed in this PR
This PR refactors the test-words layout engine out of the legacy test-ui.ts module and into the SolidJS Words component. Words now renders the #words element itself and owns its layout via a new createWordsLayout factory (words-layout.ts). test-ui.ts no longer queries #words; instead it drives layout through flush intents (newWords, wordChange, updatedWords) carried by the words-render flush hook, plus config/resize effects living inside the component. This continues the ongoing vanilla→Solid migration and better scopes layout lifetime to the component mount.
Changes:
- Moved line-jump, tape-scroll, wrapper-height, and overflow-guard logic from
test-ui.tsintowords-layout.ts, reworking DOM-child traversal into an index-basedgetLayoutItems()model backed by registered word/line-break elements. Words.tsxnow renders#words, registers line-break elements, and wires config/resize/flush effects;words-render.tsgains agenerationcounter,WordsFlushpayload, an unregisterablesetLayoutHook, and awordChangeonsetActiveWord.- Updated
before-insert-text.ts,TestPage.tsx, tests, and docs to the new module boundaries.
| File | Description |
|---|---|
frontend/src/ts/components/pages/test/words-layout.ts |
New module containing all moved layout logic (line jump, tape scroll, wrapper height, overflow guard). |
frontend/src/ts/components/pages/test/Words.tsx |
Now owns #words markup, line-break registration, and layout-driving effects/flush hook. |
frontend/src/ts/test/test-ui.ts |
Removed layout logic; delegates to Words via flush intents; keeps input-position + resize glue. |
frontend/src/ts/states/words-render.ts |
Added generation, WordsFlush, unregisterable setLayoutHook, wordChange on setActiveWord. |
frontend/src/ts/states/word-elements.ts |
Added line-break element registry for the layout. |
frontend/src/ts/states/test.ts |
Comment-only updates reflecting new ownership. |
frontend/src/ts/input/handlers/before-insert-text.ts |
Imports wouldActiveWordOverflow from new location. |
frontend/src/ts/components/pages/test/TestPage.tsx |
Drops #words markup; renders <Words>; exports/uses wordsModeClasses. |
frontend/__tests__/**, __harness__/render-words.tsx |
Tests retargeted to words-layout/new Words API; shared render harness added. |
GLOSSARY.md, docs/AGENT_BROWSER.md |
Documentation additions/clarifications. |
Review effort: Balanced
The lists open indexedDB on import; app-shell.spec imports expose in jsdom, which lacks it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
…and wordsWrapperRef Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
… words layout The reset hook runs synchronously, so a reset inside a batch no longer needs the flush to catch up: drops the store's generation and its effect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
reset -> dropOldWordsLayout, relayout -> layOutAll, inline updateLayout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
…earWords Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
…n their flush Freeing the words for the result needs no reset: a line jump completing after it finds nothing to remove, and the next new words drop its state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
replaceWords renders the new words and runs the onWordsFlushed hook right away, so the restart no longer resets, appends and flushes, and the newWords flag consumed by the next flush is gone. resetWordsRender only frees the words. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
…st-ui The layout moves the words input onto the active word itself, so Words loses its onActiveWordLaidOut prop and test-ui its wordsWrapper ref. keepWordsInputInTheCenter moves next to the input element. Drops the highlightMode effect: highlight mode is only a class on #words now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
The layout publishes the vertical middle of the active word's letters, the input centers on it in CSS. The layout no longer reads the input element. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
… drop them from test-ui Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
# Conflicts: # frontend/src/ts/states/words-render.ts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
… typo hints one Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qmCFS2TL6XhqNY5dLX78e
…o the active word The window scroll moves from WordsInput into the words layout pass: it follows the active word after the input is positioned and recenters after a resize or on focus requests. It only runs during a test; before it, the test block is scrolled into view. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…w gotcha Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every caller already runs outside a batch or effect: the flush from its animation frame, replaceWords after restart's awaits. Drops the throwaway root and effect per flush; replaceWords documents the constraint instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Words only keeps the active line in view during a test, so resizing an idle test page no longer recentered #typingTest. TestPage does it now, as the spec places it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
layoutAll already snaps it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… words It always measured from the left edge of the next letter, so the caret looked off by a letter's width in right to left tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # docs/specs/solid-monkeytype/roadmap.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Words renders #words and owns its layout (createWordsLayout). test-ui
drives it through intents (layout, relayout, resized, reset...) instead
of querying #words. Logic moved unchanged.
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com