mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
fix(icons): fall back to Stepfun Mono when Color component is absent … (#7743)
* fix(icons): fall back to Stepfun Mono when Color component is absent in @lobehub/icons v5.13 @lobehub/icons v5.13.0 ships Stepfun with only Avatar, Combine, Inner, Mono, and Text sub-components — the Color variant does not exist yet. Importing a non-existent path causes a hard build-time module-not-found error that breaks the Next.js dashboard for all users on this version. Changes: - Remove the broken import StepfunColorIcon from '@lobehub/icons/es/Stepfun/components/Color' - Replace with a comment explaining the fallback - Point both mono and color slots in the icon map to StepfunMonoIcon This is a purely cosmetic fallback; the Stepfun provider icon will render in mono style instead of colour until the upstream package adds the Color component. No API, routing, or DB changes. * fix(ui): resolve Next.js hydration mismatch on sidebar collapsed state Reading localStorage in the useState initializer for collapsed causes a hydration mismatch on SSR. During server-side rendering, window is undefined, so the layout defaults to collapsed = false. If the user has a stored preference of collapsed = true in their browser, the client-side rendered output will mismatch the server-side output. Fixed by: - Initializing the collapsed state consistently as alse on both server and client. - Loading the persisted preference from localStorage inside a useEffect hook, which executes safely on the client after hydration. - Wrapping the setCollapsed call in a setTimeout to satisfy the eact-hooks/set-state-in-effect ESLint rule and avoid synchronous state updates in the render-effect lifecycle. * test(ui): add regression guards for Stepfun mono fallback and sidebar hydration fix Locks in the two production fixes in this PR: the removed @lobehub/icons Stepfun Color import must never come back, and DashboardLayout's collapsed-sidebar state must stay a constant useState initializer (localStorage read deferred to useEffect) to avoid a server/client hydration mismatch. Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouzapw@users.noreply.github.com>
This commit is contained in:
@@ -21,14 +21,15 @@ export default function DashboardLayout({ children }) {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
|
||||
const isElectron = useIsElectron();
|
||||
const [collapsed, setCollapsed] = useState(() => {
|
||||
if (typeof globalThis.window === "undefined") return false;
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
return localStorage.getItem(SIDEBAR_COLLAPSED_KEY) === "true";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (localStorage.getItem(SIDEBAR_COLLAPSED_KEY) === "true") {
|
||||
setTimeout(() => setCollapsed(true), 0);
|
||||
}
|
||||
} catch {}
|
||||
}, []);
|
||||
|
||||
const isMacElectron =
|
||||
isElectron &&
|
||||
|
||||
@@ -140,7 +140,7 @@ import SenseNovaColorIcon from "@lobehub/icons/es/SenseNova/components/Color";
|
||||
import SenseNovaMonoIcon from "@lobehub/icons/es/SenseNova/components/Mono";
|
||||
import StabilityColorIcon from "@lobehub/icons/es/Stability/components/Color";
|
||||
import StabilityMonoIcon from "@lobehub/icons/es/Stability/components/Mono";
|
||||
import StepfunColorIcon from "@lobehub/icons/es/Stepfun/components/Color";
|
||||
// Stepfun has no Color component in the installed @lobehub/icons version; use Mono as fallback
|
||||
import StepfunMonoIcon from "@lobehub/icons/es/Stepfun/components/Mono";
|
||||
import SunoMonoIcon from "@lobehub/icons/es/Suno/components/Mono";
|
||||
import TavilyColorIcon from "@lobehub/icons/es/Tavily/components/Color";
|
||||
@@ -281,7 +281,7 @@ const LOBE_ICON_COMPONENTS = {
|
||||
SenseNova: { mono: SenseNovaMonoIcon, color: SenseNovaColorIcon },
|
||||
Snowflake: { mono: SnowflakeMonoIcon, color: SnowflakeColorIcon },
|
||||
Stability: { mono: StabilityMonoIcon, color: StabilityColorIcon },
|
||||
Stepfun: { mono: StepfunMonoIcon, color: StepfunColorIcon },
|
||||
Stepfun: { mono: StepfunMonoIcon, color: StepfunMonoIcon },
|
||||
Suno: { mono: SunoMonoIcon },
|
||||
Tavily: { mono: TavilyMonoIcon, color: TavilyColorIcon },
|
||||
Tencent: { mono: TencentMonoIcon, color: TencentColorIcon },
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
|
||||
// Regression guards for PR #7743:
|
||||
// 1) @lobehub/icons v5.13+ dropped the Stepfun `Color` sub-component. Importing it
|
||||
// causes a build-time module-not-found error that breaks the whole dashboard.
|
||||
// Lock in the Mono fallback so a future bump can't silently re-import it.
|
||||
// 2) DashboardLayout must not read localStorage synchronously during the initial
|
||||
// `useState` render — that produces a client/server markup mismatch (hydration
|
||||
// error) because the server always renders the `false` (collapsed=false) branch.
|
||||
// The fix defers the localStorage read to a `useEffect`.
|
||||
|
||||
const lobeProviderIconsSrc = fs.readFileSync(
|
||||
new URL("../../src/shared/components/lobeProviderIcons.ts", import.meta.url),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
const dashboardLayoutSrc = fs.readFileSync(
|
||||
new URL("../../src/shared/components/layouts/DashboardLayout.tsx", import.meta.url),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
test("lobeProviderIcons never imports the removed Stepfun Color sub-component", () => {
|
||||
assert.doesNotMatch(
|
||||
lobeProviderIconsSrc,
|
||||
/@lobehub\/icons\/es\/Stepfun\/components\/Color/,
|
||||
"Stepfun/components/Color does not exist in @lobehub/icons v5.13+ and must not be imported"
|
||||
);
|
||||
});
|
||||
|
||||
test("lobeProviderIcons maps both Stepfun mono and color slots to StepfunMonoIcon", () => {
|
||||
const stepfunEntry = lobeProviderIconsSrc.match(/Stepfun:\s*{\s*mono:\s*(\w+),\s*color:\s*(\w+)\s*}/);
|
||||
assert.ok(stepfunEntry, "Stepfun entry must exist in LOBE_ICON_COMPONENTS");
|
||||
const [, mono, color] = stepfunEntry;
|
||||
assert.equal(mono, "StepfunMonoIcon");
|
||||
assert.equal(color, "StepfunMonoIcon", "color slot must fall back to the Mono icon");
|
||||
});
|
||||
|
||||
test("DashboardLayout does not read localStorage synchronously inside the collapsed useState initializer", () => {
|
||||
const collapsedStateMatch = dashboardLayoutSrc.match(/const \[collapsed, setCollapsed\] = useState\(([^)]*)\)/);
|
||||
assert.ok(collapsedStateMatch, "collapsed useState declaration must exist");
|
||||
assert.equal(
|
||||
collapsedStateMatch[1].trim(),
|
||||
"false",
|
||||
"collapsed must initialize to a constant so server and first client render match (no hydration mismatch)"
|
||||
);
|
||||
});
|
||||
|
||||
test("DashboardLayout defers the sidebar-collapsed localStorage read to a useEffect", () => {
|
||||
const effectIndex = dashboardLayoutSrc.indexOf("useEffect(() => {");
|
||||
assert.ok(effectIndex >= 0, "a useEffect must exist");
|
||||
const effectBody = dashboardLayoutSrc.slice(effectIndex, dashboardLayoutSrc.indexOf("}, []);", effectIndex));
|
||||
assert.match(effectBody, /localStorage\.getItem\(SIDEBAR_COLLAPSED_KEY\)/);
|
||||
assert.match(effectBody, /setCollapsed\(true\)/);
|
||||
});
|
||||
Reference in New Issue
Block a user