Skip to content

perf: optimize mathjs lazy loading and debouncing#9

Merged
VariableThe merged 2 commits into
mainfrom
perf-optimizations
Jun 20, 2026
Merged

perf: optimize mathjs lazy loading and debouncing#9
VariableThe merged 2 commits into
mainfrom
perf-optimizations

Conversation

@VariableThe

@VariableThe VariableThe commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Summary of Changes

  • Bundle Size Optimization: Lazily loaded mathjs across the app to reduce initial bundle size. It is now correctly code-split into its own async chunk.
  • Render Stutter Fixes: Debounced AST math evaluations across both App.tsx and plugins.ts (300ms) to prevent UI stutter during rapid typing of variables or formulas.
  • Memory Optimization: Initialized OpenAI as a singleton to reduce object creation overhead during repeated AI command invocations.
  • Code Quality: Cleared explicit any types throughout the React components and hooks to improve type safety.
  • Distribution Optimization: Enabled "compression": "maximum" in the electron-builder config for smaller release payloads.

Bundle Size Comparison

Before

dist/index.html                     0.51 kB │ gzip:   0.31 kB
dist/assets/index-Cf-uibUY.css     15.19 kB │ gzip:   3.69 kB
dist/assets/openai-v7kVyr49.js    130.21 kB │ gzip:  31.31 kB
dist/assets/index-oooO25kW.js   1,701.55 kB │ gzip: 528.85 kB

After

dist/index.html                     0.51 kB │ gzip:   0.31 kB
dist/assets/index-Cf-uibUY.css     15.19 kB │ gzip:   3.69 kB
dist/assets/openai-v7kVyr49.js    130.21 kB │ gzip:  31.31 kB
dist/assets/esm-DNLxK5ck.js       733.76 kB │ gzip: 200.38 kB
dist/assets/index-BTFx5-er.js   1,046.95 kB │ gzip: 345.38 kB

Impact

  • index.js initial load payload was reduced by ~650 KB (uncompressed) / ~180 KB (gzipped).
  • The mathjs chunk is only fetched when variables or equations are used.

Pre-PR Checks

  • Linting passes with 0 errors
  • Vitest tests pass

Summary by CodeRabbit

  • Documentation

    • Updated performance audit reflecting improvements in bundle size, battery efficiency, and memory usage
  • Refactor

    • Optimized application startup through lazy loading and dynamic code splitting
    • Enhanced overall type safety throughout the codebase
    • Implemented debounced variable calculations to improve editor responsiveness during rapid typing
  • Tests

    • Improved test setup type safety
  • Chores

    • Updated build configuration for maximum compression

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@VariableThe, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 54 minutes and 19 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 60a06be2-025b-42d4-98e1-85ee5bac97e1

📥 Commits

Reviewing files that changed from the base of the PR and between b4b60d4 and 9aee209.

📒 Files selected for processing (2)
  • src/App.tsx
  • src/hooks/useVariables.ts
📝 Walkthrough

Walkthrough

Replaces static mathjs imports with lazy dynamic loading in useVariables, plugins.ts, and App.tsx. Adds a debounced /var evaluation pipeline in the editor decoration plugin and a debounced math calculation path in handleEditorChange. Introduces a module-level OpenAI client singleton re-initialized only on key/URL changes. Tightens TypeScript types in GraphView, useNoteStorage, and plugins.ts. Adds compression: "maximum" to electron-builder config and updates PERFORMANCE_AUDIT.md.

Changes

Performance optimizations and type improvements

Layer / File(s) Summary
Lazy mathjs loading and debounced /var evaluation in plugins and useVariables
src/hooks/useVariables.ts, src/lib/editor/plugins.ts
Removes static mathjs imports; introduces module-level globalScopeCache, scopeEvalTimeout, and lastDocString in plugins.ts; replaces synchronous scope building with a 300ms-debounced async pipeline that dynamically imports mathjs, parses /var directives, evaluates expressions with fallback, updates the cache, and forces a re-render; coerces VariableWidget values to strings; types SyntaxNode placeholders.
Debounced math calculation in handleEditorChange
src/App.tsx
Removes static mathjs import; adds mathCalcTimeoutRef; rewrites handleEditorChange to debounce, dynamically import mathjs, compute scope from window.__globalVariables plus /var directives, and apply results via targeted view.dispatch changes instead of rewriting the entire document string.
OpenAI client singleton caching
src/App.tsx
Introduces module-level openaiInstance, currentApiKey, and currentApiBaseUrl; re-creates the client only when the key or normalized base URL changes; uses the singleton for chat completions; adjusts completion and error handlers to call handleEditorChange with a single argument.
TypeScript type tightening
src/GraphView.tsx, src/hooks/useNoteStorage.ts, src/Settings.test.tsx
Narrows GraphViewProps.notes and the internal links array from any[] to typed shapes; types handleNodeClick and nodeCanvasObject parameters; tightens handleOpenNote to cast Event to CustomEvent; adds inline eslint-disable on the window.electronAPI mock cast.
Build config and audit doc
package.json, PERFORMANCE_AUDIT.md
Adds compression: "maximum" to electron-builder config; rewrites PERFORMANCE_AUDIT.md sections to reflect Excellent status, lazy loading, debounced /var engine, OpenAI SDK singleton, and 0 lint errors.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • VariableThe/PaperCache#7: Modifies the same /ai//ctx command execution path and OpenRouter base URL handling in src/App.tsx that this PR also changes with the OpenAI singleton refactor.

Poem

🐇 Hop hop, no more eager loads at dawn,
mathjs sleeps until the equation's drawn.
One OpenAI client, cached and neat,
Debounced at 300ms — no repeat!
The audit glows Excellent in the light,
A bunny optimized your app tonight. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main performance optimizations: lazy loading of mathjs and debouncing of recalculations, which are the primary changes across multiple files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf-optimizations

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
PERFORMANCE_AUDIT.md (1)

36-40: 💤 Low value

Fix minor style issues: remove redundancy and capitalize proper noun.

Line 37–38 contains two style issues:

  1. "completely eliminating" is redundant; "eliminating" alone conveys the same meaning.
  2. "markdown documents" should be "Markdown" (proper noun, the name of the markup language).
✨ Proposed style fixes
 **Reactive `/var` Engine:**
 * Variable scopes and AST mathematical evaluations are debounced (300ms) within `App.tsx` and CodeMirror decorations (`plugins.ts`).
-* CodeMirror view decorations dynamically render synchronous outputs using a globally cached state of variable scopes. Updates trigger asynchronously, completely eliminating synchronous rendering stalls during rapid typing in massive markdown documents.
+* CodeMirror view decorations dynamically render synchronous outputs using a globally cached state of variable scopes. Updates trigger asynchronously, eliminating synchronous rendering stalls during rapid typing in massive Markdown documents.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PERFORMANCE_AUDIT.md` around lines 36 - 40, In the PERFORMANCE_AUDIT.md file
under the Reactive `/var` Engine section, fix two style issues: remove the
redundant word "completely" from the phrase "completely eliminating synchronous
rendering stalls" (leaving just "eliminating"), and capitalize "markdown
documents" to "Markdown documents" since Markdown is a proper noun referring to
the markup language by name.

Source: Linters/SAST tools

src/App.tsx (2)

35-35: ⚡ Quick win

Type the OpenAI client instance.

Static analysis flags any. Import the OpenAI type for better type safety:

♻️ Suggested fix
+import type OpenAI from 'openai'
+
-let openaiInstance: any = null
+let openaiInstance: OpenAI | null = null
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/App.tsx` at line 35, The openaiInstance variable is declared with the
`any` type, which bypasses type safety checks. Import the OpenAI type from the
openai package and replace the `any` type annotation on the openaiInstance
declaration with the proper OpenAI client type to enable static type checking
and improve code safety.

Source: Linters/SAST tools


200-200: ⚡ Quick win

Type the dynamically imported mathjs module.

Same issue as in plugins.ts - static analysis flags any:

♻️ Suggested fix
-          let mathjs: any
+          let mathjs: { evaluate: (expr: string, scope?: Record<string, unknown>) => unknown }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/App.tsx` at line 200, The variable mathjs is typed as any, which prevents
static analysis from catching type-related issues. Replace the any type with the
proper type definition for the dynamically imported mathjs module. Import the
appropriate type from the mathjs package (such as MathJsStatic or similar) and
use it to type the mathjs variable instead of any, similar to the fix already
applied in plugins.ts.

Source: Linters/SAST tools

src/lib/editor/plugins.ts (1)

109-109: ⚡ Quick win

Type the dynamically imported mathjs module.

Static analysis flags any here. Since you're only using evaluate, provide a minimal type:

♻️ Suggested fix
-          let mathjs: any
+          let mathjs: { evaluate: (expr: string, scope?: Record<string, unknown>) => unknown }
           try {
             mathjs = await import('mathjs')
           } catch {
             return
           }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/editor/plugins.ts` at line 109, The variable mathjs is typed as any,
which triggers static analysis warnings. Replace the any type with a minimal
type definition that only includes the evaluate method, since that is the only
property being used from the dynamically imported mathjs module. Create a simple
interface or type with just the evaluate method signature to provide proper type
safety while avoiding the use of any.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/App.tsx`:
- Around line 484-492: The catch clause type annotation is invalid. TypeScript
only allows `any` or `unknown` as catch clause type annotations, not union types
like `Error | unknown`. Change the type annotation in the catch block that
handles the editor state error (the one with docStr and errorVal variables) from
`Error | unknown` to simply `unknown`. The existing type cast `(err as Error)`
when accessing the error message will continue to work correctly.

In `@src/hooks/useVariables.ts`:
- Around line 9-34: The syncVars() function in the useEffect hook has a race
condition where multiple concurrent invocations can complete out-of-order. To
fix this, add an abort flag variable that gets set to true in a cleanup function
returned from the useEffect. Before the assignment to window.__globalVariables
at the end of syncVars(), check if the abort flag is true and skip the update if
it is. This ensures that only the most recent invocation of syncVars() will
update the global variables, preventing stale data from overwriting newer values
when notes change rapidly.

---

Nitpick comments:
In `@PERFORMANCE_AUDIT.md`:
- Around line 36-40: In the PERFORMANCE_AUDIT.md file under the Reactive `/var`
Engine section, fix two style issues: remove the redundant word "completely"
from the phrase "completely eliminating synchronous rendering stalls" (leaving
just "eliminating"), and capitalize "markdown documents" to "Markdown documents"
since Markdown is a proper noun referring to the markup language by name.

In `@src/App.tsx`:
- Line 35: The openaiInstance variable is declared with the `any` type, which
bypasses type safety checks. Import the OpenAI type from the openai package and
replace the `any` type annotation on the openaiInstance declaration with the
proper OpenAI client type to enable static type checking and improve code
safety.
- Line 200: The variable mathjs is typed as any, which prevents static analysis
from catching type-related issues. Replace the any type with the proper type
definition for the dynamically imported mathjs module. Import the appropriate
type from the mathjs package (such as MathJsStatic or similar) and use it to
type the mathjs variable instead of any, similar to the fix already applied in
plugins.ts.

In `@src/lib/editor/plugins.ts`:
- Line 109: The variable mathjs is typed as any, which triggers static analysis
warnings. Replace the any type with a minimal type definition that only includes
the evaluate method, since that is the only property being used from the
dynamically imported mathjs module. Create a simple interface or type with just
the evaluate method signature to provide proper type safety while avoiding the
use of any.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f909d651-0877-4c5e-9a7a-6154640ba3a5

📥 Commits

Reviewing files that changed from the base of the PR and between ebb4223 and b4b60d4.

📒 Files selected for processing (8)
  • PERFORMANCE_AUDIT.md
  • package.json
  • src/App.tsx
  • src/GraphView.tsx
  • src/Settings.test.tsx
  • src/hooks/useNoteStorage.ts
  • src/hooks/useVariables.ts
  • src/lib/editor/plugins.ts

Comment thread src/App.tsx Outdated
Comment thread src/hooks/useVariables.ts
@VariableThe VariableThe merged commit ef84064 into main Jun 20, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant