Skip to content

🔧 chore: upgrade to Next.js 16 and update ESLint config#104

Open
cojocaru-david wants to merge 1 commit into
masterfrom
chore/update-nextjs
Open

🔧 chore: upgrade to Next.js 16 and update ESLint config#104
cojocaru-david wants to merge 1 commit into
masterfrom
chore/update-nextjs

Conversation

@cojocaru-david
Copy link
Copy Markdown
Owner

  • Upgrade next to 16.1.6 and align eslint-config-next
  • Update React to 19.2.4 and related type packages
  • Bump various dependencies (drizzle-orm, gsap, lucide-react, pg, zod, etc.)
  • Refactor eslint.config.mjs to use direct next/core-web-vitals and next/typescript imports
  • Remove eslint.ignoreDuringBuilds from next.config.ts

- Upgrade next to 16.1.6 and align eslint-config-next
- Update React to 19.2.4 and related type packages
- Bump various dependencies (drizzle-orm, gsap, lucide-react, pg, zod, etc.)
- Refactor eslint.config.mjs to use direct next/core-web-vitals and next/typescript imports
- Remove eslint.ignoreDuringBuilds from next.config.ts
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
snippetslibrary-v2 Error Error Mar 2, 2026 2:40pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 2, 2026

Summary by CodeRabbit

  • New Features

    • Enhanced OG image generation with improved visual styling, gradients, and layout
    • Improved combobox component responsiveness with dynamic width handling
  • Chores

    • Version bumped to 0.2.1
    • Updated major dependencies including Next.js 16.1.6 and React 19.2.4 for improved performance
    • Migrated linting tooling configuration

Walkthrough

This PR encompasses a comprehensive project modernization cycle: ESLint configuration simplification, Next.js version upgrade (15.5.3 → 16.1.6) with associated dependency updates, OG image generation component rewrite, UI component refinements, theme context initialization simplification, and TypeScript JSX runtime configuration update.

Changes

Cohort / File(s) Summary
Build & Configuration
eslint.config.mjs, next.config.ts, tsconfig.json
Replaced FlatCompat-based ESLint setup with direct preset imports; removed explicit eslint.ignoreDuringBuilds configuration; updated JSX runtime to "react-jsx" and expanded TypeScript include patterns.
Dependency Management
package.json
Version bump to 0.2.1; updated Next.js (15.5.3 → 16.1.6), React (19.1.1 → 19.2.4), and numerous ecosystem packages; changed lint script from "next lint" to "eslint ."; added db management scripts.
UI Components
src/components/ui/TextType.tsx, src/components/ui/avatar.tsx, src/components/ui/combobox.tsx
TextType: replaced textColors prop with dynamic ElementType as prop and refactored rendering for intrinsic/custom components. Avatar: removed ESLint disable comment. Combobox: added triggerWidth state with ResizeObserver for dynamic width handling on menu open.
Application Logic
src/app/api/og/route.tsx, src/contexts/ThemeContext.tsx
OG route heavily redesigned with new layout structure, gradient decorations, component reordering, and dynamic author rendering. Theme context: removed isLoading state, replaced effect-based initialization with lazy useState initializers.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Configs dance, dependencies leap,
Next.js grows tall while themes run deep,
OG images glow with gradient flair,
Components refactored with TypeScript care,
A version bump blooms—0.2.1 springs to life!

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main objectives of the changeset: upgrading to Next.js 16 and updating the ESLint configuration, which are the primary changes evident across the modified files.
Description check ✅ Passed The description is directly related to the changeset and covers the key changes: Next.js upgrade, React updates, dependency bumps, ESLint config refactoring, and removal of eslint.ignoreDuringBuilds setting.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/update-nextjs

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/api/og/route.tsx (1)

464-467: ⚠️ Potential issue | 🟠 Major

ETag validation occurs after image generation, negating caching benefit.

The if-none-match check at lines 464-467 happens after the ImageResponse is already created at line 142. This means the expensive image generation runs on every request, even when a 304 response could be returned immediately.

Move the ETag check before image generation to avoid unnecessary work.

🐛 Proposed fix to check ETag before generation
     const cacheKey = `og-${title}-${description}-${language}-${selectedTheme}-${authorName}`;
+    const etag = `"${Buffer.from(cacheKey).toString("base64")}"`;
+
+    const ifNoneMatch = request.headers.get("if-none-match");
+    if (ifNoneMatch === etag) {
+      return new Response(null, { status: 304 });
+    }

     const response = new ImageResponse(
       // ... image generation code ...
     );

     response.headers.set(
       "Cache-Control",
       "public, s-maxage=86400, stale-while-revalidate=43200",
     );
     response.headers.set("CDN-Cache-Control", "public, s-maxage=86400");
     response.headers.set("Vercel-CDN-Cache-Control", "public, s-maxage=86400");
-
-    const etag = `"${Buffer.from(cacheKey).toString("base64")}"`;
     response.headers.set("ETag", etag);
-
-    const ifNoneMatch = request.headers.get("if-none-match");
-    if (ifNoneMatch === etag) {
-      return new Response(null, { status: 304 });
-    }

     return response;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/og/route.tsx` around lines 464 - 467, The ETag check is performed
after creating the expensive ImageResponse (variable ImageResponse) so caching
is ineffective; move the if-none-match/ifNoneMatch check to run before you
construct ImageResponse: compute the same etag value using only request
inputs/params (the same inputs used to build the image) without instantiating
ImageResponse, check if request.headers.get("if-none-match") equals etag and
return new Response(null, { status: 304 }) immediately if it matches, otherwise
proceed to generate the ImageResponse; update/keep the etag variable and the
ifNoneMatch comparison logic (variables etag and ifNoneMatch) in the request
handler so the early-return happens prior to calling new ImageResponse.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 83: Upgrade to "shiki": "^4.0.1" requires Node.js ≥ 20; verify and
enforce this by checking your runtime environments (local dev, CI/CD, and
production), update package.json engines to "node": ">=20" if appropriate, and
ensure any Docker images/CI runners are using Node 20+; also confirm
src/lib/shiki.ts usages (createHighlighter, codeToHtml, transformers,
loadLanguage, loadTheme) are compatible with Shiki 4.x in your tests and update
CI matrix to run against Node 20 to catch incompatibilities early.

In `@src/components/ui/combobox.tsx`:
- Around line 145-165: The effect that syncs the combobox trigger width is
adding both a ResizeObserver and a window "resize" listener, which is redundant;
remove the window.addEventListener("resize", updateWidth) and its corresponding
window.removeEventListener cleanup from the useEffect that depends on open, and
rely solely on the ResizeObserver (created for triggerRef.current) to call
updateWidth and the existing cleanup that calls resizeObserver.disconnect();
keep references to updateWidth, triggerRef, setTriggerWidth, ResizeObserver and
the open dependency unchanged.

In `@src/components/ui/TextType.tsx`:
- Around line 202-218: The wrapper <span> currently receives containerRef and
fixed classNames while intrinsic elements get the ref and componentProps
directly, causing startOnVisible to observe the wrapper and layout mismatches;
update the non-string branch so instead of wrapping in a span you inject
containerRef and any needed className/whitespace props into componentProps (so
the custom component receives ref via componentProps and the same styling),
ensuring custom components forward refs (e.g., via forwardRef) or support React
19 ref-as-prop; keep using createElement(Component, componentProps, content)
after adding the ref and className to componentProps.

In `@src/contexts/ThemeContext.tsx`:
- Around line 41-44: The resolvedTheme state initialization causes SSR/CSR
mismatch; change the resolvedTheme useState in ThemeContext.tsx to a stable
server-safe default (e.g., "light") instead of calling
resolveTheme(getInitialTheme()), and add a client-only effect (useEffect) that
runs on mount to compute the real value via resolveTheme(getInitialTheme()) and
call setResolvedTheme; reference the existing variables/functions theme,
resolvedTheme, setResolvedTheme, getInitialTheme(), and resolveTheme to locate
and update the code.
- Line 96: The ThemeContext currently hardcodes isLoading: false which can cause
flashes; restore a loading state in the context and ThemeProvider so consumers
can wait for initialization—add an isLoading boolean to the context default and
state in ThemeProvider (e.g., ThemeContext, ThemeProvider, initializeTheme or
the useEffect that reads persisted theme), set isLoading=true until the theme is
applied, then set isLoading=false after initialization completes; alternatively
ensure server-rendered/default CSS/theme is neutral if you intentionally keep
isLoading removed.

---

Outside diff comments:
In `@src/app/api/og/route.tsx`:
- Around line 464-467: The ETag check is performed after creating the expensive
ImageResponse (variable ImageResponse) so caching is ineffective; move the
if-none-match/ifNoneMatch check to run before you construct ImageResponse:
compute the same etag value using only request inputs/params (the same inputs
used to build the image) without instantiating ImageResponse, check if
request.headers.get("if-none-match") equals etag and return new Response(null, {
status: 304 }) immediately if it matches, otherwise proceed to generate the
ImageResponse; update/keep the etag variable and the ifNoneMatch comparison
logic (variables etag and ifNoneMatch) in the request handler so the
early-return happens prior to calling new ImageResponse.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

📥 Commits

Reviewing files that changed from the base of the PR and between 1a8538c and 8171353.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • eslint.config.mjs
  • next.config.ts
  • package.json
  • src/app/api/og/route.tsx
  • src/components/ui/TextType.tsx
  • src/components/ui/avatar.tsx
  • src/components/ui/combobox.tsx
  • src/contexts/ThemeContext.tsx
  • tsconfig.json
💤 Files with no reviewable changes (2)
  • next.config.ts
  • src/components/ui/avatar.tsx

Comment thread package.json
"eslint-config-next": "16.1.6",
"prettier": "^3.8.1",
"esbuild": "^0.27.3",
"shiki": "^4.0.1",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check Shiki usage patterns in the codebase
echo "=== Shiki imports and usage ==="
rg -n 'from ["\']shiki' --type=ts --type=tsx -g '!node_modules'

echo ""
echo "=== Shiki API calls ==="
rg -n -A3 'getHighlighter|createHighlighter|codeToHtml' --type=ts --type=tsx -g '!node_modules'

Repository: cojocaru-david/snippetslibrary.com

Length of output: 195


🏁 Script executed:

#!/bin/bash
# Check Shiki usage patterns in the codebase
echo "=== Shiki imports and usage ==="
rg -n 'from.*shiki' --type=ts --type=tsx -g '!node_modules'

echo ""
echo "=== Shiki API calls ==="
rg -n 'getHighlighter|createHighlighter|codeToHtml' --type=ts --type=tsx -g '!node_modules'

echo ""
echo "=== Checking src/lib/shiki.ts specifically ==="
ls -la src/lib/shiki.ts 2>/dev/null || echo "File not found in src/lib/"

echo ""
echo "=== Searching for shiki files ==="
fd 'shiki' --type=f -g '!node_modules'

Repository: cojocaru-david/snippetslibrary.com

Length of output: 458


🏁 Script executed:

#!/bin/bash
# Check Shiki usage patterns in the codebase
echo "=== Shiki imports and usage ==="
rg -n 'from.*shiki|import.*shiki' -g '!node_modules'

echo ""
echo "=== Reading src/lib/shiki.ts ==="
cat -n src/lib/shiki.ts

echo ""
echo "=== Shiki API calls (getHighlighter, createHighlighter, codeToHtml) ==="
rg -n 'getHighlighter|createHighlighter|codeToHtml' -g '!node_modules'

Repository: cojocaru-david/snippetslibrary.com

Length of output: 31632


🌐 Web query:

Shiki 4.x breaking changes migration guide

💡 Result:

Shiki 4.x breaking changes (v3 → v4) & migration checklist

  1. Upgrade Node.js first
  • Shiki v4 requires Node.js ≥ 20 (Node 18 is not supported). Update your runtime/CI before bumping Shiki. [1]
  1. Fix removed typo’d API names (now fully removed in v4)
  • Type renamed (typo removed):
    • CreatedBundledHighlighterOptionsCreateBundledHighlighterOptions [1]
  • Function renamed (typo removed):
    • createdBundledHighlightercreateBundledHighlighter [1]
  1. If you use @shikijs/vitepress-twoslash: option renamed
  • TwoslashFloatingVue no longer accepts singular theme; use themes instead. [1]
  1. If you style Twoslash “query persisted” UI: CSS class typo removed
  • twoslash-query-presisted (misspelled) is removed; use twoslash-query-persisted. [1]

Suggested upgrade path

  • Upgrade to the latest v3 first, then bump to v4 and apply the changes above (Shiki recommends step-by-step migration by major version). [2]

Sources: Shiki v4 migration notes [1], Shiki migration guide [2].


🌐 Web query:

Shiki 4.0 createHighlighter themes langs parameters API

💡 Result:

createHighlighter() (Shiki v4)

import { createHighlighter } from 'shiki'

const highlighter = await createHighlighter({
  themes: ['nord', 'github-dark'], // array (no singular `theme`)
  langs: ['js', 'ts'],            // array
})
  • themes: an array of theme names (bundled) and/or custom theme objects. [1][2]
  • langs: an array of language IDs to preload (must be loaded before use, or load later via highlighter.loadLanguage(...)). [1]
  • Note (breaking change): createHighlighter({ theme: ... }) is dropped—use themes: [...]. [3]

Fine‑grained / core API (when you want explicit imports + engine control)

import { createHighlighterCore } from 'shiki/core'
import { createOnigurumaEngine } from 'shiki/engine/oniguruma'

const highlighter = await createHighlighterCore({
  themes: [import('@shikijs/themes/nord')],
  langs: [import('@shikijs/langs/javascript')],
  engine: createOnigurumaEngine(() => import('shiki/wasm')),
})
  • themes: theme modules/objects you import yourself. [4]
  • langs: language modules/objects you import yourself. [4]
  • engine: required for the sync core API, and commonly provided with core setups (Oniguruma/WASM or JS RegExp engine). [4][5]

Sources:
[1] Shiki “Installation & Usage” (createHighlighter with themes/langs, loadTheme/loadLanguage) (shiki.style)
[2] Shiki “Load Custom Themes” (passing a Theme object via themes) (shiki.matsu.io)
[3] Shiki “Migration” (theme option dropped; use themes array) (shiki.matsu.io)
[4] Shiki @shikijs/markdown-it docs (core highlighter with themes/langs imports + engine) (shiki.matsu.io)
[5] Shiki “Synchronous Usage” (core sync requires plain-object themes/langs + explicit engine) (shiki.style)

Citations:


🌐 Web query:

Shiki 4.0 codeToHtml transformers API

💡 Result:

In Shiki v4, codeToHtml() takes a transformers option: an array of ShikiTransformer objects that let you modify output by hooking into different stages of tokenization + HAST (HTML AST) generation. [1]

Basic usage

import { codeToHtml } from 'shiki'

const html = await codeToHtml(code, {
  lang: 'ts',
  theme: 'vitesse-light',
  transformers: [
    {
      name: 'my-transformer',
      code(node) {
        this.addClassToHast(node, 'language-ts')
      },
      line(node, line) {
        node.properties['data-line'] = line
      },
      span(node, line, col) {
        node.properties['data-token'] = `token:${line}:${col}`
      },
    },
  ],
})

[1]

Transformer hooks (API surface)

A ShikiTransformer can implement any of these hooks: preprocess, tokens, span, line, code, pre, root, postprocess. [1]

Ordering

You can force ordering with enforce: 'pre' | 'post' (otherwise it runs in normal order). [1]

Meta access (in supported integrations)

Inside a transformer you can read e.g. this.options.meta.__raw. [1]

v4 note

Shiki v4.0.0 mainly removes deprecated APIs and requires Node >= 20; the Transformers approach remains the way to customize HTML output. [2]

Sources: Transformers guide [1], Shiki v4.0.0 announcement [2].


Verify Node.js ≥ 20 requirement for Shiki 4.x upgrade.

Shiki 4.x requires Node.js ≥ 20 (previously ≥ 18). Verify that your deployment environment, CI/CD pipeline, and development setup support Node.js 20 or later. The API usage in src/lib/shiki.ts (createHighlighter, codeToHtml, transformers, loadLanguage, loadTheme, etc.) is compatible with Shiki 4.x.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 83, Upgrade to "shiki": "^4.0.1" requires Node.js ≥ 20;
verify and enforce this by checking your runtime environments (local dev, CI/CD,
and production), update package.json engines to "node": ">=20" if appropriate,
and ensure any Docker images/CI runners are using Node 20+; also confirm
src/lib/shiki.ts usages (createHighlighter, codeToHtml, transformers,
loadLanguage, loadTheme) are compatible with Shiki 4.x in your tests and update
CI matrix to run against Node 20 to catch incompatibilities early.

Comment on lines +145 to +165
React.useEffect(() => {
if (!open) return;

const updateWidth = () => {
setTriggerWidth(triggerRef.current?.offsetWidth);
};

updateWidth();

const triggerElement = triggerRef.current;
if (!triggerElement) return;

const resizeObserver = new ResizeObserver(updateWidth);
resizeObserver.observe(triggerElement);
window.addEventListener("resize", updateWidth);

return () => {
resizeObserver.disconnect();
window.removeEventListener("resize", updateWidth);
};
}, [open]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Redundant window resize listener.

The ResizeObserver already detects size changes caused by window resizing, making the separate window.addEventListener("resize", ...) unnecessary. Removing it simplifies the code and avoids duplicate width calculations.

♻️ Proposed simplification
   React.useEffect(() => {
     if (!open) return;

     const updateWidth = () => {
       setTriggerWidth(triggerRef.current?.offsetWidth);
     };

     updateWidth();

     const triggerElement = triggerRef.current;
     if (!triggerElement) return;

     const resizeObserver = new ResizeObserver(updateWidth);
     resizeObserver.observe(triggerElement);
-    window.addEventListener("resize", updateWidth);

     return () => {
       resizeObserver.disconnect();
-      window.removeEventListener("resize", updateWidth);
     };
   }, [open]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
React.useEffect(() => {
if (!open) return;
const updateWidth = () => {
setTriggerWidth(triggerRef.current?.offsetWidth);
};
updateWidth();
const triggerElement = triggerRef.current;
if (!triggerElement) return;
const resizeObserver = new ResizeObserver(updateWidth);
resizeObserver.observe(triggerElement);
window.addEventListener("resize", updateWidth);
return () => {
resizeObserver.disconnect();
window.removeEventListener("resize", updateWidth);
};
}, [open]);
React.useEffect(() => {
if (!open) return;
const updateWidth = () => {
setTriggerWidth(triggerRef.current?.offsetWidth);
};
updateWidth();
const triggerElement = triggerRef.current;
if (!triggerElement) return;
const resizeObserver = new ResizeObserver(updateWidth);
resizeObserver.observe(triggerElement);
return () => {
resizeObserver.disconnect();
};
}, [open]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/combobox.tsx` around lines 145 - 165, The effect that syncs
the combobox trigger width is adding both a ResizeObserver and a window "resize"
listener, which is redundant; remove the window.addEventListener("resize",
updateWidth) and its corresponding window.removeEventListener cleanup from the
useEffect that depends on open, and rely solely on the ResizeObserver (created
for triggerRef.current) to call updateWidth and the existing cleanup that calls
resizeObserver.disconnect(); keep references to updateWidth, triggerRef,
setTriggerWidth, ResizeObserver and the open dependency unchanged.

Comment on lines +202 to 218
if (typeof Component === "string") {
const IntrinsicComponent = Component as React.ElementType;
return (
<IntrinsicComponent
{...componentProps}
ref={containerRef as React.Ref<HTMLElement>}
>
{cursorCharacter}
</span>
),
{content}
</IntrinsicComponent>
);
}

return (
<span ref={containerRef} className="inline-block whitespace-pre-wrap">
{createElement(Component, componentProps, content)}
</span>
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent ref handling and styling for custom components.

When Component is a custom React component (non-string), the containerRef is attached to a wrapper <span> instead of the component itself, while intrinsic elements receive the ref directly. This causes:

  1. startOnVisible observes the wrapper span, not the actual component
  2. The wrapper span has different styling ("inline-block whitespace-pre-wrap") than componentProps, leading to inconsistent layout

Consider passing ref through componentProps for custom components or documenting this behavioral difference.

💡 Suggested approach for consistent ref handling
-  if (typeof Component === "string") {
-    const IntrinsicComponent = Component as React.ElementType;
-    return (
-      <IntrinsicComponent
-        {...componentProps}
-        ref={containerRef as React.Ref<HTMLElement>}
-      >
-        {content}
-      </IntrinsicComponent>
-    );
-  }
-
-  return (
-    <span ref={containerRef} className="inline-block whitespace-pre-wrap">
-      {createElement(Component, componentProps, content)}
-    </span>
-  );
+  return createElement(
+    Component,
+    { ...componentProps, ref: containerRef },
+    content
+  );

Note: This simplified approach requires custom components to forward refs properly via forwardRef or React 19's ref-as-prop pattern.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (typeof Component === "string") {
const IntrinsicComponent = Component as React.ElementType;
return (
<IntrinsicComponent
{...componentProps}
ref={containerRef as React.Ref<HTMLElement>}
>
{cursorCharacter}
</span>
),
{content}
</IntrinsicComponent>
);
}
return (
<span ref={containerRef} className="inline-block whitespace-pre-wrap">
{createElement(Component, componentProps, content)}
</span>
);
return createElement(
Component,
{ ...componentProps, ref: containerRef },
content
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/TextType.tsx` around lines 202 - 218, The wrapper <span>
currently receives containerRef and fixed classNames while intrinsic elements
get the ref and componentProps directly, causing startOnVisible to observe the
wrapper and layout mismatches; update the non-string branch so instead of
wrapping in a span you inject containerRef and any needed className/whitespace
props into componentProps (so the custom component receives ref via
componentProps and the same styling), ensuring custom components forward refs
(e.g., via forwardRef) or support React 19 ref-as-prop; keep using
createElement(Component, componentProps, content) after adding the ref and
className to componentProps.

Comment on lines +41 to +44
const [theme, setTheme] = useState<UITheme>(() => getInitialTheme());
const [resolvedTheme, setResolvedTheme] = useState<"dark" | "light">(() =>
resolveTheme(getInitialTheme()),
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential hydration mismatch with theme initialization.

During SSR, resolveTheme(getInitialTheme()) returns "light" (since window is undefined), but on the client, it may resolve to "dark" based on localStorage or system preference. This can cause a hydration mismatch and a visible theme flash.

Consider keeping the initial resolvedTheme consistent between server and client (e.g., always start with a known value like "light") and only apply the actual resolved theme in a client-side effect.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contexts/ThemeContext.tsx` around lines 41 - 44, The resolvedTheme state
initialization causes SSR/CSR mismatch; change the resolvedTheme useState in
ThemeContext.tsx to a stable server-safe default (e.g., "light") instead of
calling resolveTheme(getInitialTheme()), and add a client-only effect
(useEffect) that runs on mount to compute the real value via
resolveTheme(getInitialTheme()) and call setResolvedTheme; reference the
existing variables/functions theme, resolvedTheme, setResolvedTheme,
getInitialTheme(), and resolveTheme to locate and update the code.

setTheme: handleSetTheme,
resolvedTheme,
isLoading,
isLoading: false,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded isLoading: false may cause theme flash.

The removal of the loading state means consumers can no longer conditionally render a loading indicator while the theme initializes. Combined with the hydration mismatch above, users may see a brief flash of the wrong theme on page load.

If this is intentional to simplify the API, ensure the server-rendered HTML uses a neutral or expected default theme, or consider using CSS to hide content until the theme is applied.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contexts/ThemeContext.tsx` at line 96, The ThemeContext currently
hardcodes isLoading: false which can cause flashes; restore a loading state in
the context and ThemeProvider so consumers can wait for initialization—add an
isLoading boolean to the context default and state in ThemeProvider (e.g.,
ThemeContext, ThemeProvider, initializeTheme or the useEffect that reads
persisted theme), set isLoading=true until the theme is applied, then set
isLoading=false after initialization completes; alternatively ensure
server-rendered/default CSS/theme is neutral if you intentionally keep isLoading
removed.

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