[Claude Code Testing] Creating a Rich Text Editor
A Rust/Dioxus Desktop App
[Claude Code Testing] Creating a Rich Text Editor
A Rust/Dioxus Desktop App

Source
[embed]
I just tried out Claude code, and I’ve got to say that I’ve found it useful! Here is my opinion:
To begin testing Claude code, I decided to use it to help me develop a rich text editor that I’ve been building. Why a rich text editor? Well, first, it is a hard problem. For context, the Monaco rich text editor, written in JavaScript, it over 90,000 lines of code. There is a reason why Cursor didn’t built their own IDE. But also, building a rich text editor requires having a World Model of “events” effecting the application’s visual state
A rich text editor is basically a text editor that dynamically formats or styles the text based on some syntax rule. For example, in Markdown, “# Heading” will have larger text than “## Heading 2.”
But why is this so hard? This is actually my 3rd attempt that trying to build a rich text editor with Dioxus in Rust. I was able to embed a JS editor into an app, but this didn’t have the granularity that I wanted for syntax highlighting. My previous attempts had run into a “brick wall” because of my design couldn’t scale in complexity. However, after coming up with a promising design pattern, I decided to see it through with Claude at my copilot.
^ My new strategy was to create a sparse matrix for the text and then to style each cell of this matrix based on the output of a syntax parser. I started off really simple… two cells in the same row, can I move the arrow between them? Can I add text? Etc. This design lead to further improvements down the road.
To start, I wanted to see if Claude could help me with two tasks:
- Move the “key down handling” logic to a module
- Create new key down logic
^ I have previously created the logic for Enter, ArrowLeft, ArrowRight, and Character inputs. Also, I had already moved the Enter key down logic to a “handler” module.
The test was to move key-down logic for ArrowLeft, ArrowRight, and CharacterInput logic into the handler module.
Note: I did a code review before committing every change that Claude made.
Prompt #1:
in handle_keydown_input move the logic for all keydown inputs that are not enter and directional arrows into the handler.rs module. Create a function called handle_character_input and add a match condition on event for Key::Character. Also add another match condition for backspace but do not add a function for this yet.
Note: I found it kind of annoying that sometimes I need to exit out/compare changes when Claude makes changes.
So, this Claude code was able to move the logic from the main.rs to the handler.rs file! However, it didn’t update the main.rs file to use the new module function and it strangely added a new function that it didn’t use/need in handler.rs.
It was able to move my CharacterInput logic into the handler module also!
So, let’s try something a bit harder… The next test was to create ArrowUp and ArrowDown from scratch.
Prompt #2:
in the handler.rs module, I want to create a handler_up_arrow function and to add a matching case in handle_keydown_input
This is a harder problem than the previous one because we need to track where the cursor position is.
For example, in handle left arrow, if the cursor position is zero and its the first column, we want to move the cursor the the last column of the previous row.
But the logic for ArrowUp and ArrowDown should be simpler than ArrowLeft and ArrowRight because it has fewer edge cases.
After running the prompt, I noticed that the code wasn’t taking into account the length of the text in each column and row. But it was able to create runnable code! It didn’t need to be perfect on the first try, and it was a starting point at least.
Prompt #3:
create a down arrow similar to the up arrow logic
This was able to copy and reverse the logic. Claude code is very nice so far!
Prompt #4:
now handle the backspace logic
This task was significantly different from the enter key, directional arrows, and the character input. It also needed to delete characters potentially move the cursor position at edge cases.
This is where Claude code and I hit a brick wall. And this wasn’t really Claude code’s fault. I tried to vibe-code to a solution but it wasn’t working…
I needed to tweak my design which was 1) to use a “fat struct” to update text in the DOM sequentially in an event loop 2) isolate syntax parsing from DOM updates. However, I was able to target the areas that I needed to change with Claude code.
Conclusion
I think Claude code was particularly useful when I needed to repeat logic, but I’m actually really surprised at how useful it can be to implement boilerplate syntax and how it easy was to tweak.
Although, I can see it can be abused and how people might get lazy. I almost caught myself trying out random things without thinking but, I think it is perfect when you’re half asleep and just need to do some mindless refactoring. It is about as useful as you can be explicit.
This GitHub Repo for this editor can be found here.
Here is my Claude generated summary of this editor’s capabilities:
Input Handlers
Enter Key (handle_enter_key)
- Capability: Splits text at cursor position, creates new row
- Logic: Takes text before cursor, keeps it in current cell; moves text after cursor + remaining columns to new
row
- DOM Operations: update_text, create_row, update_text_cursor
- Edge Cases: Handles splitting at any position within text
Backspace (handle_backspace)
- Capability: Complex backspace logic with multiple scenarios
- Scenarios:
a. pos=0, i>0, j==0: Merge current row with previous row, delete current row
b. pos=0, i>0, j>0: Move to previous column (currently incomplete)
c. pos=1, j>0: Merge current cell with previous cell, delete character
d. pos=0: Regular text sync
e. Default: Normal character deletion
- DOM Operations: update_row, delete_row, update_text, update_text_cursor
- Edge Cases: Row merging, column merging, text concatenation
Arrow Keys
- Left Arrow (handle_left_arrow): Navigate left with cell/row boundaries
- Right Arrow (handle_right_arrow): Navigate right with cell/row boundaries
- Up Arrow (handle_up_arrow): Navigate up maintaining column position
- Down Arrow (handle_down_arrow): Navigate down maintaining column position
- Edge Cases: Boundary handling, text length preservation, focus management
Character Input (handle_character_input)
- Capability: Insert characters at cursor position
- DOM Operations: update_text_cursor
- Edge Cases: Cursor position validation, text insertion
DOM Update Operations
Text Operations
- update_text: Update cell content without cursor
- update_text_cursor: Update cell content with cursor positioning
- focus_element: Focus element with cursor position
Row Operations
- create_row: Create new row and renumber subsequent rows
- delete_row: Delete row and renumber subsequent rows
- update_row: Replace row content without affecting other rows
Cell Operations
- create_cell: Create individual cell with specific index
- delete_element: Remove specific element from DOM and raw_text
System Operations
- internal_process: Boolean signal to prevent recursive update_syntax calls
JavaScript Functions
Caret/Focus Management
- getCaretClickPosition: Get cursor position within element
- focusElement: Basic element focus
- focusElementAndSetCaret: Focus with cursor positioning
- clearElementTextWithPosition: Update text with cursor positioning
DOM Manipulation
- deleteElement: Remove element from DOM
- clearElementText: Clear element content
- getElementText: Get element text content
Row/Cell Management
- deleteRow: Delete row and renumber subsequent elements
- createRow: Create new row with proper insertion and renumbering
- createCell: Create individual cell in specified row
- updateRow: Replace entire row content without affecting other rows
Edge Cases & Special Handling
Index Management
- Row/column indexing with textarea-{i}-{j} format
- Automatic renumbering after row operations
- Bounds checking for array access
State Synchronization
- editor.raw_text vs visual DOM consistency
- internal_process signal prevents recursive updates
- Queue-based DOM updates with async processing
Text Processing
- Empty element removal from multi-element rows
- Character insertion/deletion at specific positions
- Text splitting and merging across cells/rows
Focus & Cursor
- Cross-browser cursor positioning
- ContentEditable vs input element handling
- Focus management during DOM updates
Concurrency
- Spawn-based async operations
- DOM update queue processing
- Wait mechanisms for pending updates
Error Handling
- Element existence validation
- Bounds checking for text operations
- Fallback behaviors for cursor positioning
System Architecture
- Signal-based state management with use_signal
- Coroutine-based async processing for DOM updates
- Event-driven keyboard handling with preventDefault
- Queue-based DOM operation batching
- JavaScript interop via document::eval
The system handles complex text editing scenarios with proper state synchronization between Rust backend and
JavaScript DOM manipulation. 메타데이터
- post_id
- b803af9fa3f9
- slug
- claude-code-testing-creating-a-rich-text-editor-b803af9fa3f9
- url
- https://medium.com/lazy-by-design/claude-code-testing-creating-a-rich-text-editor-b803af9fa3f9
- canonical_url
- https://medium.com/lazy-by-design/claude-code-testing-creating-a-rich-text-editor-b803af9fa3f9
- author_url
- https://medium.com/@rohankotwani
- status
- ok
- fetched_at
- 2026-07-18 19:47:45