Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
>> 2026-03-27 19:24:41 UTC - Optimized SidebarPhaseGroup.tsx to avoid passing Set objects as props, resolving React.memo equality breakdown and preventing global re-renders on individual lesson completion state changes. Reconstructed the Set locally during render using O(1) strings.
>> 2026-03-28 19:32:26 UTC - Optimized ConceptGraph.tsx to wrap the initial nodes and edges calculations within a useMemo hook based on getAllLessons(). This prevents repetitive O(N) evaluations, mitigates D3 force simulation garbage collection pressure, and reduces layout shifting upon component re-renders.
>> - 2026-04-01 | Vite Chunking Optimization | Merged `react-dom` into `react-vendor` chunk. This optimization prevents runtime ESM initialization errors from highly coupled React core libraries and slightly improves Time To Interactive (TTI) by reducing HTTP waterfalls. Validated via `npm run analyze` bundle visualization.
- Resolved a critical React rendering race condition in `MarkdownRenderer.tsx` by replacing the globally shared, stateful `glossaryRegex` (which mutated `lastIndex`) with a stateless, deterministic `Array.from(text.matchAll(new RegExp(...)))` implementation. This prevents unpredictable UI bugs and missing glossary tooltips during Concurrent Mode renders while maintaining excellent V8 execution performance.
13 changes: 7 additions & 6 deletions src/components/MarkdownRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,9 @@ function createHeadingComponent(Tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6') {
}
}

// Use global flag 'g' to iterate over matches without string slicing
const glossaryRegex = new RegExp(getGlossaryRegex().source, 'gi')
// ⚡ Bolt: Removed 'g' flag and global RegExp instance to prevent stateful RegExp.lastIndex race conditions
// during React Concurrent Mode rendering. Replaced with stateless Array.from(matchAll()).
const glossaryRegexSource = getGlossaryRegex().source
const glossaryDefinitionsByLowerTerm = Object.fromEntries(
Object.entries(glossaryTerms).map(([term, definition]) => [term.toLowerCase(), definition]),
)
Expand All @@ -199,13 +200,13 @@ function addGlossaryTooltips(text: string): (string | JSX.Element)[] {
const parts: (string | JSX.Element)[] = []
const matched = new Set<string>()
let lastIndex = 0
let match
let keyIdx = 0

// Reset lastIndex for the shared regex instance
glossaryRegex.lastIndex = 0
// Create a new RegExp instance per call to ensure pure, deterministic rendering.
const regex = new RegExp(glossaryRegexSource, 'gi')
const matches = Array.from(text.matchAll(regex))

while ((match = glossaryRegex.exec(text)) !== null) {
for (const match of matches) {
const termLower = match[1]!.toLowerCase()
const matchStart = match.index
const matchEnd = match.index + match[0].length
Expand Down
Loading