Skip to content

fix(web): stabilize homepage loading and build pipeline#4210

Closed
tianyou-lab wants to merge 1 commit intoQuantumNous:mainfrom
tianyou-lab:codex/homepage-fix
Closed

fix(web): stabilize homepage loading and build pipeline#4210
tianyou-lab wants to merge 1 commit intoQuantumNous:mainfrom
tianyou-lab:codex/homepage-fix

Conversation

@tianyou-lab
Copy link
Copy Markdown

@tianyou-lab tianyou-lab commented Apr 13, 2026

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • Closes # (如有)

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

(请在此粘贴截图、关键日志或测试报告,以证明变更生效)

Summary by CodeRabbit

  • Bug Fixes

    • Improved error displays now show detailed diagnostic information including component stack traces for better troubleshooting.
  • Improvements

    • Endpoint selector changed from scroll-based picker to clickable pill buttons for the first six endpoints.
    • Enhanced home page language and theme handling for externally loaded content.
  • Chores

    • Optimized build process with increased Node.js memory allocation.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 13, 2026

Walkthrough

The pull request refactors the frontend build pipeline and home page functionality. Changes include switching from bun run build to explicit bunx vite build with disabled minification, converting lazy-loaded Home component to static import, enhancing error diagnostics in ErrorBoundary, reworking home page content handling and endpoint selection UI, and conditionally including Vite plugins only during development.

Changes

Cohort / File(s) Summary
Build Configuration
Dockerfile, web/vite.config.js
Dockerfile switches frontend build to bunx vite build --minify=false with increased Node.js heap size. Vite config now conditionally includes codeInspectorPlugin only during serve mode.
Component Import & Error Handling
web/src/App.jsx, web/src/components/common/ErrorBoundary.jsx
Home component changed from lazy-loaded to static import while retaining Suspense wrapper. ErrorBoundary enhanced to capture and display errorMessage and componentStack in diagnostic UI panel.
Home Page Refactoring
web/src/pages/Home/index.jsx
Removed ScrollList UI and notice modal. Replaced scroll-based endpoint picker with clickable pill buttons for first 6 endpoints. Reworked external home page URL handling with language/theme fallbacks. Changed content parsing to normalize fetched data as string before marked.parse.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 With vite's explicit cli, our builds run lean,
Endpoints as pills, not scrolls in between,
Error bounds capture the tales gone astray,
Home page speaks external—hurray, hurray!
Configuration blooms when serve calls—hooray!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: improving homepage loading stability and optimizing the build pipeline configuration across multiple files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Contributor

@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: 6

Caution

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

⚠️ Outside diff range comments (1)
web/src/pages/Home/index.jsx (1)

92-96: ⚠️ Potential issue | 🟠 Major

Copy the same URL that you display.

The input now shows the selected endpoint, but the copy action still writes only serverAddress. After the user picks a path, the pasted URL no longer matches the visible field.

Proposed fix
+  const selectedEndpoint = endpointItems[endpointIndex]?.value || '';
+
   const handleCopyBaseURL = async () => {
-    const ok = await copy(serverAddress);
+    const ok = await copy(`${serverAddress}${selectedEndpoint}`);
     if (ok) {
       showSuccess(t('已复制到剪切板'));
     }
   };
@@
-                      value={`${serverAddress}${endpointItems[endpointIndex] || ''}`}
+                      value={`${serverAddress}${selectedEndpoint}`}

Also applies to: 132-141

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

In `@web/src/pages/Home/index.jsx` around lines 92 - 96, The copy handler
currently copies only serverAddress; update handleCopyBaseURL (and the analogous
copy handler around lines 132-141) to copy the full visible URL shown in the
input (concatenate serverAddress with the currently selected path/endpoint or
read the input's current value) instead of always copying serverAddress so the
pasted URL matches what the user sees; locate and change the copy(...) call in
handleCopyBaseURL (and the corresponding second handler) to use the
computedDisplayedUrl/currentInputValue used to render the field.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Dockerfile`:
- Line 9: Replace the direct invocation "bunx vite build --minify=false" in the
Dockerfile RUN line with the canonical frontend entrypoint by running "bun run
build" (preserving the DISABLE_ESLINT_PLUGIN, NODE_OPTIONS, and
VITE_REACT_APP_VERSION env values around the command), and move any Vite/minify
tweaks into the frontend's package.json build script or Vite config (so that the
"build" script in package.json and Vite config control minification/flags rather
than bypassing them); ensure Docker uses Bun as the package manager/runner for
consistency with "bun install", "bun run dev", "bun run build", and "bun run
i18n:*".

In `@web/src/components/common/ErrorBoundary.jsx`:
- Around line 58-64: The headings "Error" and "Component Stack" in the
ErrorBoundary component should be routed through i18n: import and use the
useTranslation hook inside the ErrorBoundary component (referencing
ErrorBoundary, errorMessage, and componentStack) and replace the hardcoded
strings with t('中文 key for Error') and t('中文 key for Component Stack'); ensure
the chosen Chinese keys are added to the locale JSONs under
web/src/i18n/locales/{lang}.json as flat keys and call t(...) where the divs
render the headings.
- Around line 24-27: The ErrorBoundary currently stores and renders raw
errorMessage and componentStack (see componentDidCatch/setState setting
errorMessage and componentStack and the fallback render block around lines
53-70); change it to avoid exposing details to users by gating detailed info
behind a dev/debug flag: in componentDidCatch only save full error data when a
debug flag is true (use a prop like debug or an env check such as
process.env.NODE_ENV !== 'production'), otherwise set a generic user-facing
message and clear componentStack; update the fallback render to conditionally
show the detailed panel only when debug is enabled and always render the generic
fallback for production users.

In `@web/src/pages/Home/index.jsx`:
- Around line 80-81: The iframe handshake in Home/index.jsx is forcing themeMode
to 'light' which breaks dark/auto syncing; update the postMessage to send the
current theme value (use the same actualTheme / theme variable used in
useHeaderBar.js) instead of the hardcoded 'light', and add a safe fallback
(e.g., 'light' only if actualTheme is undefined) so
iframe.contentWindow.postMessage uses the real theme state.
- Around line 72-82: The postMessage handler is being attached before the iframe
is rendered because setHomePageContent(content) is async; update the logic so
messaging is attached after the iframe actually mounts: after calling
setHomePageContent (and when rawContent.startsWith('https://')), move the iframe
query/iframe.onload registration into a follow-up effect or callback that runs
after the DOM updates (e.g., a useEffect that depends on the state updated by
setHomePageContent or a ref callback for the iframe), locate the code around
setHomePageContent, rawContent, document.querySelector('iframe') and
iframe.onload, and ensure you add and clean up the load listener before calling
iframe.contentWindow.postMessage({ themeMode: 'light' }) and postMessage({ lang:
currentLanguage }).
- Around line 132-160: endpointItems contains objects like { value }, but the
JSX treats each item as a string, causing [object Object] rendering and
duplicate keys; update the input value to use
endpointItems[endpointIndex]?.value (e.g.,
value={`${serverAddress}${endpointItems[endpointIndex]?.value || ''}`), change
the map to use .map((endpoint, idx) => ...) and use key={endpoint.value},
display {endpoint.value}, compare against endpointItems[endpointIndex]?.value
for active styling, and setEndpointIndex(idx) in the onClick handler (avoid
indexOf on objects).

---

Outside diff comments:
In `@web/src/pages/Home/index.jsx`:
- Around line 92-96: The copy handler currently copies only serverAddress;
update handleCopyBaseURL (and the analogous copy handler around lines 132-141)
to copy the full visible URL shown in the input (concatenate serverAddress with
the currently selected path/endpoint or read the input's current value) instead
of always copying serverAddress so the pasted URL matches what the user sees;
locate and change the copy(...) call in handleCopyBaseURL (and the corresponding
second handler) to use the computedDisplayedUrl/currentInputValue used to render
the field.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 564bb291-cc11-45ce-b822-0585d4a60c67

📥 Commits

Reviewing files that changed from the base of the PR and between b81d342 and cc9ad67.

📒 Files selected for processing (5)
  • Dockerfile
  • web/src/App.jsx
  • web/src/components/common/ErrorBoundary.jsx
  • web/src/pages/Home/index.jsx
  • web/vite.config.js

Comment thread Dockerfile
COPY ./web .
COPY ./VERSION .
RUN DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(cat VERSION) bun run build
RUN DISABLE_ESLINT_PLUGIN='true' NODE_OPTIONS='--max-old-space-size=4096' VITE_REACT_APP_VERSION=$(cat VERSION) bunx vite build --minify=false
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.

⚠️ Potential issue | 🟠 Major

Keep the frontend image build on bun run build.

Calling bunx vite build here sidesteps the repo’s canonical frontend build entrypoint, so Docker can silently diverge from local/CI builds once package.json adds flags, wrappers, or pre/post hooks. Please fold the Vite/minify tweak into the normal build script/config instead of bypassing it.

Based on learnings: Use Bun as the preferred package manager and script runner for the frontend (web/ directory). Use bun install, bun run dev, bun run build, and bun run i18n:* commands.

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

In `@Dockerfile` at line 9, Replace the direct invocation "bunx vite build
--minify=false" in the Dockerfile RUN line with the canonical frontend
entrypoint by running "bun run build" (preserving the DISABLE_ESLINT_PLUGIN,
NODE_OPTIONS, and VITE_REACT_APP_VERSION env values around the command), and
move any Vite/minify tweaks into the frontend's package.json build script or
Vite config (so that the "build" script in package.json and Vite config control
minification/flags rather than bypassing them); ensure Docker uses Bun as the
package manager/runner for consistency with "bun install", "bun run dev", "bun
run build", and "bun run i18n:*".

Comment on lines +24 to +27
this.setState({
errorMessage: error?.message || String(error || ''),
componentStack: errorInfo?.componentStack || '',
});
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.

⚠️ Potential issue | 🟠 Major

Don't expose raw exception details to every user.

Rendering errorMessage and especially componentStack in the fallback leaks internal implementation details on production crashes. Please gate this panel behind a dev/debug flag and keep the default user-facing fallback generic.

Also applies to: 53-70

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

In `@web/src/components/common/ErrorBoundary.jsx` around lines 24 - 27, The
ErrorBoundary currently stores and renders raw errorMessage and componentStack
(see componentDidCatch/setState setting errorMessage and componentStack and the
fallback render block around lines 53-70); change it to avoid exposing details
to users by gating detailed info behind a dev/debug flag: in componentDidCatch
only save full error data when a debug flag is true (use a prop like debug or an
env check such as process.env.NODE_ENV !== 'production'), otherwise set a
generic user-facing message and clear componentStack; update the fallback render
to conditionally show the detailed panel only when debug is enabled and always
render the generic fallback for production users.

Comment on lines +58 to +64
<div className='text-sm font-semibold mb-2'>Error</div>
<div className='text-xs text-semi-color-text-1'>{errorMessage}</div>
{componentStack && (
<>
<div className='text-sm font-semibold mt-4 mb-2'>
Component Stack
</div>
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.

⚠️ Potential issue | 🟡 Minor

Localize the new diagnostics headings.

If this panel stays, Error and Component Stack should also go through t(...); they currently bypass the app’s i18n flow.

As per coding guidelines: web/src/**/*.{ts,tsx,js,jsx}: Frontend i18n: Use i18next + react-i18next + i18next-browser-languagedetector. Translation files in web/src/i18n/locales/{lang}.json must be flat JSON with Chinese source strings as keys. Use useTranslation() hook and call t('中文key') in components.

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

In `@web/src/components/common/ErrorBoundary.jsx` around lines 58 - 64, The
headings "Error" and "Component Stack" in the ErrorBoundary component should be
routed through i18n: import and use the useTranslation hook inside the
ErrorBoundary component (referencing ErrorBoundary, errorMessage, and
componentStack) and replace the hardcoded strings with t('中文 key for Error') and
t('中文 key for Component Stack'); ensure the chosen Chinese keys are added to the
locale JSONs under web/src/i18n/locales/{lang}.json as flat keys and call t(...)
where the divs render the headings.

Comment on lines 72 to 82
setHomePageContent(content);
localStorage.setItem('home_page_content', content);

// 如果内容是 URL,则发送主题模式
if (data.startsWith('https://')) {
if (rawContent.startsWith('https://')) {
const iframe = document.querySelector('iframe');
if (iframe) {
iframe.onload = () => {
iframe.contentWindow.postMessage({ themeMode: actualTheme }, '*');
iframe.contentWindow.postMessage({ lang: i18n.language }, '*');
iframe.contentWindow.postMessage({ themeMode: 'light' }, '*');
iframe.contentWindow.postMessage({ lang: currentLanguage }, '*');
};
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.

⚠️ Potential issue | 🟠 Major

Attach iframe messaging after the iframe is rendered.

setHomePageContent(content) only schedules the rerender. On a cold load, querySelector('iframe') runs before the iframe exists, so the onload handler is never registered and the external page usually never receives the initial lang payload.

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

In `@web/src/pages/Home/index.jsx` around lines 72 - 82, The postMessage handler
is being attached before the iframe is rendered because
setHomePageContent(content) is async; update the logic so messaging is attached
after the iframe actually mounts: after calling setHomePageContent (and when
rawContent.startsWith('https://')), move the iframe query/iframe.onload
registration into a follow-up effect or callback that runs after the DOM updates
(e.g., a useEffect that depends on the state updated by setHomePageContent or a
ref callback for the iframe), locate the code around setHomePageContent,
rawContent, document.querySelector('iframe') and iframe.onload, and ensure you
add and clean up the load listener before calling
iframe.contentWindow.postMessage({ themeMode: 'light' }) and postMessage({ lang:
currentLanguage }).

Comment on lines +80 to +81
iframe.contentWindow.postMessage({ themeMode: 'light' }, '*');
iframe.contentWindow.postMessage({ lang: currentLanguage }, '*');
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.

⚠️ Potential issue | 🟠 Major

Don't hardcode the iframe theme to light.

This regresses dark/auto mode for external homepages. web/src/hooks/common/useHeaderBar.js:106-117 already uses actualTheme for iframe sync, but this code now forces 'light' during the homepage handshake.

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

In `@web/src/pages/Home/index.jsx` around lines 80 - 81, The iframe handshake in
Home/index.jsx is forcing themeMode to 'light' which breaks dark/auto syncing;
update the postMessage to send the current theme value (use the same actualTheme
/ theme variable used in useHeaderBar.js) instead of the hardcoded 'light', and
add a safe fallback (e.g., 'light' only if actualTheme is undefined) so
iframe.contentWindow.postMessage uses the real theme state.

Comment on lines +132 to +160
value={`${serverAddress}${endpointItems[endpointIndex] || ''}`}
className='flex-1 !rounded-full'
size={isMobile ? 'default' : 'large'}
suffix={
<div className='flex items-center gap-2'>
<ScrollList
bodyHeight={32}
style={{ border: 'unset', boxShadow: 'unset' }}
>
<ScrollItem
mode='wheel'
cycled={true}
list={endpointItems}
selectedIndex={endpointIndex}
onSelect={({ index }) => setEndpointIndex(index)}
/>
</ScrollList>
<Button
type='primary'
onClick={handleCopyBaseURL}
icon={<IconCopy />}
className='!rounded-full'
/>
</div>
<Button
type='primary'
onClick={handleCopyBaseURL}
icon={<IconCopy />}
className='!rounded-full'
/>
}
/>
</div>
{endpointItems.length > 0 && (
<div className='flex flex-wrap justify-center gap-2 mt-4 max-w-3xl'>
{endpointItems.slice(0, 6).map((endpoint) => (
<button
key={endpoint}
type='button'
className={`px-3 py-1.5 rounded-full text-xs md:text-sm border transition-colors ${
endpoint === endpointItems[endpointIndex]
? 'bg-blue-500 text-white border-blue-500'
: 'bg-transparent text-semi-color-text-1 border-semi-color-border'
}`}
onClick={() =>
setEndpointIndex(endpointItems.indexOf(endpoint))
}
>
{endpoint}
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.

⚠️ Potential issue | 🟠 Major

Render the endpoint value, not the whole object.

endpointItems contains { value } objects, so ${endpointItems[endpointIndex]}, key={endpoint}, and {endpoint} all stringify to [object Object]. That breaks the displayed base URL and gives every button the same React key.

Proposed fix
-                      value={`${serverAddress}${endpointItems[endpointIndex] || ''}`}
+                      value={`${serverAddress}${endpointItems[endpointIndex]?.value || ''}`}
@@
-                      {endpointItems.slice(0, 6).map((endpoint) => (
+                      {endpointItems.slice(0, 6).map(({ value }) => (
                         <button
-                          key={endpoint}
+                          key={value}
                           type='button'
                           className={`px-3 py-1.5 rounded-full text-xs md:text-sm border transition-colors ${
-                            endpoint === endpointItems[endpointIndex]
+                            value === endpointItems[endpointIndex]?.value
                               ? 'bg-blue-500 text-white border-blue-500'
                               : 'bg-transparent text-semi-color-text-1 border-semi-color-border'
                           }`}
-                          onClick={() =>
-                            setEndpointIndex(endpointItems.indexOf(endpoint))
-                          }
+                          onClick={() =>
+                            setEndpointIndex(
+                              endpointItems.findIndex((item) => item.value === value),
+                            )
+                          }
                         >
-                          {endpoint}
+                          {value}
                         </button>
                       ))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/Home/index.jsx` around lines 132 - 160, endpointItems contains
objects like { value }, but the JSX treats each item as a string, causing
[object Object] rendering and duplicate keys; update the input value to use
endpointItems[endpointIndex]?.value (e.g.,
value={`${serverAddress}${endpointItems[endpointIndex]?.value || ''}`), change
the map to use .map((endpoint, idx) => ...) and use key={endpoint.value},
display {endpoint.value}, compare against endpointItems[endpointIndex]?.value
for active styling, and setEndpointIndex(idx) in the onClick handler (avoid
indexOf on objects).

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