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
15 changes: 8 additions & 7 deletions src/shared/components/layouts/DashboardLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down
4 changes: 2 additions & 2 deletions src/shared/components/lobeProviderIcons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 },

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.

medium

Since the LobeIconEntry type defines color as optional (color?: LobeIconComponent) and getLobeProviderIcon automatically falls back to mono when color is undefined, we can simply omit the color property for Stepfun instead of duplicating StepfunMonoIcon. This is consistent with other mono-only icons in this map (e.g., Suno, Ai21, Anthropic).

Suggested change
Stepfun: { mono: StepfunMonoIcon, color: StepfunMonoIcon },
Stepfun: { mono: StepfunMonoIcon },

Suno: { mono: SunoMonoIcon },
Tavily: { mono: TavilyMonoIcon, color: TavilyColorIcon },
Tencent: { mono: TencentMonoIcon, color: TencentColorIcon },
Expand Down
Original file line number Diff line number Diff line change
@@ -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\)/);
});