Example Walkthrough: Multiple Duplicates Rendering
Example Input
Text: "ذهبو الى المدرسة ثم ذهبو الى البيت ثم ذهبو مرة اخرى"
Step 1: Backend Analysis
When this text is sent to /api/analyze, the backend returns:
{
"original": "ذهبو الى المدرسة ثم ذهبو الى البيت ثم ذهبو مرة اخرى",
"corrected": "ذهبوا الى المدرسة ثم ذهبوا الى البيت ثم ذهبوا مرة اخرى",
"suggestions": [
{
"start": 0,
"end": 4,
"original": "ذهبو",
"correction": "ذهبوا",
"type": "spelling"
},
{
"start": 20,
"end": 24,
"original": "ذهبو",
"correction": "ذهبوا",
"type": "spelling"
},
{
"start": 38,
"end": 42,
"original": "ذهبو",
"correction": "ذهبوا",
"type": "spelling"
}
],
"status": "success"
}
Step 2: Offset Verification
Character positions in the text:
Position: 0 5 10 15 20 25 30 35 40 45 50
Text: ذ ــ ـ م ذ ــ ـ ب ذ ــ ى
ذهبو الى المدرسة ثم ذهبو الى البيت ثم ذهبو مرة اخرى
^^^^ ^^^^ ^^^^
0-4 20-24 38-42
Step 3: Renderer Processing
3a. Sort Suggestions (already sorted)
sorted = [
{start: 0, end: 4, ...}, // First occurrence
{start: 20, end: 24, ...}, // Second occurrence
{start: 38, end: 42, ...} // Third occurrence
]
3b. Create Segments
The text is split into segments at suggestion boundaries:
segments = [
{type: 'suggestion', text: 'ذهبو', suggestion: {...}}, // [0:4]
{type: 'text', text: ' الى المدرسة ثم '}, // [4:20]
{type: 'suggestion', text: 'ذهبو', suggestion: {...}}, // [20:24]
{type: 'text', text: ' الى البيت ثم '}, // [24:38]
{type: 'suggestion', text: 'ذهبو', suggestion: {...}}, // [38:42]
{type: 'text', text: ' مرة اخرى'} // [42:51]
]
3c. Render Each Segment
Segment 1 (suggestion):
<span class="spelling-error"
data-suggestion-id="0"
data-original="ذهبو"
data-correction="ذهبوا"
data-type="spelling"
title="spelling: ذهبوا">
ذهبو
</span>
Segment 2 (text): " الى المدرسة ثم "
Segment 3 (suggestion):
<span class="spelling-error"
data-suggestion-id="1"
data-original="ذهبو"
data-correction="ذهبوا"
data-type="spelling"
title="spelling: ذهبوا">
ذهبو
</span>
Segment 4 (text): " الى البيت ثم "
Segment 5 (suggestion):
<span class="spelling-error"
data-suggestion-id="2"
data-original="ذهبو"
data-correction="ذهبوا"
data-type="spelling"
title="spelling: ذهبوا">
ذهبو
</span>
Segment 6 (text): " مرة اخرى"
Step 4: Final Rendered Output
Complete HTML:
<span class="spelling-error" data-suggestion-id="0" data-original="ذهبو" data-correction="ذهبوا" data-type="spelling" title="spelling: ذهبوا">ذهبو</span> الى المدرسة ثم <span class="spelling-error" data-suggestion-id="1" data-original="ذهبو" data-correction="ذهبوا" data-type="spelling" title="spelling: ذهبوا">ذهبو</span> الى البيت ثم <span class="spelling-error" data-suggestion-id="2" data-original="ذهبو" data-correction="ذهبوا" data-type="spelling" title="spelling: ذهبوا">ذهبو</span> مرة اخرى
Visual representation:
[ذهبو] الى المدرسة ثم [ذهبو] الى البيت ثم [ذهبو] مرة اخرى
(red) (red) (red)
Step 5: User Interaction
User clicks on first "ذهبو" (red underline)
- Click event fired on
<span role="0"> - Tooltip appears showing:
- Error type: "خطأ إملائي"
- Correction: "ذهبوا"
- User can click to apply correction
When user applies correction:
Before:
ذهبو الى المدرسة ثم ذهبو الى البيت ثم ذهبو مرة اخرى
After (1st corrected):
ذهبوا الى المدرسة ثم ذهبو الى البيت ثم ذهبو مرة اخرى
Key: Only the clicked occurrence changes. The other two remain.
Important:
- ✅ Cursor position is preserved
- ✅ Selection is preserved
- ✅ Text is re-analyzed immediately
- ✅ New highlights appear with updated offsets
Advantages Over Old System
Old System (replace-based)
// Try to find and replace first occurrence
text.replace("ذهبو", "ذهبوا")
// Result: ALL occurrences get replaced (or just first depending on regex)
// Problem: Can't target specific occurrence
// Problem: Duplicate words fail silently
New System (offset-based)
// Render all occurrences with specific offsets
render({
text: "ذهبو الى ... ذهبو الى ... ذهبو مرة",
suggestions: [
{start: 0, end: 4, ...}, // Exact position 1
{start: 20, end: 24, ...}, // Exact position 2
{start: 38, end: 42, ...} // Exact position 3
]
})
// Result: 3 independent spans, each clickable/correctable
// Advantage: Precise, no ambiguity, no silent failures
Code Flow Diagram
User Text Input
↓
┌─────────────────────────────────┐
│ saveSelection() from selection.js│
│ Stores: {selectionStart, end} │
└────────────────┬────────────────┘
↓
┌─────────────────────────────────┐
│ analyzeText() from editor.js │
│ Calls: POST /api/analyze │
└────────────────┬────────────────┘
↓
API Response
{original, corrected,
suggestions with offsets}
↓
┌─────────────────────────────────┐
│ render() from renderer.js │
│ Input: text + suggestions[] │
│ - Sort by offset │
│ - Create segments │
│ - Escape HTML │
│ - Generate spans with metadata │
│ Output: Safe HTML string │
└────────────────┬────────────────┘
↓
┌─────────────────────────────────┐
│ setEditorHTML() from selection.js│
│ Updates DOM │
└────────────────┬────────────────┘
↓
┌─────────────────────────────────┐
│ restoreSelection() from selection│
│ Restores {selectionStart, end} │
│ Cursor back where it was! │
└──────────────────────────────────┘
Per-Occurrence Data
Each <span> carries its own suggestion:
Span 1:
data-suggestion-id="0"- First in suggestions arraydata-original="ذهبو"data-correction="ذهبوا"- Spans characters [0:4]
Span 2:
data-suggestion-id="1"- Second in suggestions arraydata-original="ذهبو"(same original, different position)data-correction="ذهبوا"(same correction)- Spans characters [20:24]
Span 3:
data-suggestion-id="2"- Third in suggestions arraydata-original="ذهبو"(same original, different position)data-correction="ذهبوا"(same correction)- Spans characters [38:42]
Result: No confusion. Each occurrence is independent.
Summary
✅ All 3 occurrences are highlighted
✅ Each is independently identifiable
✅ User can click any one to see/apply correction
✅ Cursor/selection preserved through re-renders
✅ XSS-safe (all user content escaped)
✅ No regex or string replacement needed