Conversation
WalkthroughThis PR introduces a component preview server infrastructure within the frontend by adding a new Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! 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. Comment |
| process.exit(0); | ||
| } | ||
|
|
||
| import("child_process").then(({ execSync }) => { |
There was a problem hiding this comment.
WARNING: Missing error handling for dynamic import
The dynamic import() returns a Promise that could reject. If the import fails, the script will silently fail without any error message, making debugging difficult.
Consider adding error handling:
import("child_process").then(({ execSync }) => {
execSync("electron-builder install-app-deps", { stdio: "inherit" });
}).catch((err) => {
console.error("postinstall: failed to import child_process:", err);
process.exit(1);
});
Code Review SummaryStatus: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (12 files)
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
frontend/app/tab/tab.tsx (1)
80-235: Avoid passingnullto a mouse handler (type safety).
handleRenameTabis typed as a mouse handler but is invoked withnullfrom the context menu. Consider a small refactor to keep the types honest and avoid null usage.♻️ Suggested refactor
-const handleRenameTab: React.MouseEventHandler<HTMLDivElement> = (event) => { +const startRename = (event?: React.MouseEvent<HTMLDivElement>) => { event?.stopPropagation(); setIsEditable(true); editableTimeoutRef.current = setTimeout(() => { selectEditableText(); }, 50); }; ... - menu.push( - { label: "Rename Tab", click: () => handleRenameTab(null) }, + menu.push( + { label: "Rename Tab", click: () => startRename() }, ... - onDoubleClick={handleRenameTab} + onDoubleClick={startRename}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/app/tab/tab.tsx` around lines 80 - 235, The handler handleRenameTab is typed as a mouse event handler but the context-menu code calls it with null; fix by introducing a neutral starter function (e.g., startRenameTab) that contains the rename logic (setIsEditable, editableTimeoutRef, selectEditableText) and keeping handleRenameTab as a proper MouseEvent handler that just calls startRenameTab(event); update the context menu entry to call startRenameTab() instead of handleRenameTab(null) and leave onDoubleClick/onClick usage to call handleRenameTab so types remain correct.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/app/modals/about.tsx`:
- Around line 74-76: Remove the unused AboutModalProps interface and the empty
destructured parameter in the AboutModal component signature: change the
component to a no-props declaration (e.g., use AboutModal() or const AboutModal
= () => ...) and delete the AboutModalProps interface and the "{}:
AboutModalProps" portion so lint errors for unused/empty props are resolved.
In `@frontend/preview/preview.tsx`:
- Around line 70-75: The index/back links currently use a hardcoded href="/"
which breaks when the app is served from a subpath; update the two anchor links
(the "← index" anchors at the shown locations) to preserve the current pathname
while clearing query params instead of pointing to "/", e.g. obtain the current
path from the router or window.location.pathname and navigate to that pathname
with an empty query (use Next.js useRouter().push/replace({ pathname:
router.pathname, query: {} }) or build href from window.location.pathname) so
the link works under subpaths and only removes the query string.
- Around line 30-34: The lazy loader currently falls back to
Object.values(mod)[0] which can pick the wrong export; update the loader inside
the Object.entries(previewModules).map callback (the lazy(() =>
loader().then(...)) block) to explicitly resolve the export: if mod.default
exists use it; otherwise examine the named exports (filtering out common module
metadata like __esModule), if there's exactly one remaining named export return
that as the component; otherwise throw a clear Error that includes the preview
path/key (use pathToKey(path)) and lists available export names so the failure
is explicit and actionable.
---
Nitpick comments:
In `@frontend/app/tab/tab.tsx`:
- Around line 80-235: The handler handleRenameTab is typed as a mouse event
handler but the context-menu code calls it with null; fix by introducing a
neutral starter function (e.g., startRenameTab) that contains the rename logic
(setIsEditable, editableTimeoutRef, selectEditableText) and keeping
handleRenameTab as a proper MouseEvent handler that just calls
startRenameTab(event); update the context menu entry to call startRenameTab()
instead of handleRenameTab(null) and leave onDoubleClick/onClick usage to call
handleRenameTab so types remain correct.
ℹ️ Review info
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
Taskfile.ymlfrontend/app/modals/about.tsxfrontend/app/tab/tab.tsxfrontend/preview/index.htmlfrontend/preview/preview.cssfrontend/preview/preview.tsxfrontend/preview/previews/.gitkeepfrontend/preview/previews/modal-about.preview.tsxfrontend/preview/vite.config.tsfrontend/types/media.d.tspackage.jsonpostinstall.cjs
| interface AboutModalProps {} | ||
|
|
||
| const AboutModal = ({}: AboutModalProps) => { |
There was a problem hiding this comment.
Remove empty props interface and empty destructuring.
The empty interface and {} pattern trigger lint errors; a no-props component is clearer and lint-safe.
♻️ Suggested fix
-interface AboutModalProps {}
-
-const AboutModal = ({}: AboutModalProps) => {
+const AboutModal = () => {📝 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.
| interface AboutModalProps {} | |
| const AboutModal = ({}: AboutModalProps) => { | |
| const AboutModal = () => { |
🧰 Tools
🪛 Biome (2.4.4)
[error] 74-74: An empty interface is equivalent to {}.
(lint/suspicious/noEmptyInterface)
[error] 76-76: Unexpected empty object pattern.
(lint/correctness/noEmptyPattern)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/app/modals/about.tsx` around lines 74 - 76, Remove the unused
AboutModalProps interface and the empty destructured parameter in the AboutModal
component signature: change the component to a no-props declaration (e.g., use
AboutModal() or const AboutModal = () => ...) and delete the AboutModalProps
interface and the "{}: AboutModalProps" portion so lint errors for unused/empty
props are resolved.
| Object.entries(previewModules).map(([path, loader]) => [ | ||
| pathToKey(path), | ||
| lazy(() => | ||
| loader().then((mod) => ({ default: (mod.default ?? Object.values(mod)[0]) as React.ComponentType })) | ||
| ), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "preview.tsx" -type fRepository: wavetermdev/waveterm
Length of output: 134
🌐 Web query:
React.lazy module shape requirements what must resolved module export default
💡 Result:
React.lazy requires that the function you pass returns a Promise that resolves to a “module object” with a default export—and that default must be a valid React component (function/class). In other words, it must resolve to:
{ default: SomeComponent }React’s docs explicitly note that this pattern requires the lazily imported component to be exported as the default export. [1]
If your file only has a named export, adapt the import by mapping it into the required module shape:
const Foo = React.lazy(() =>
import('./Foo').then((mod) => ({ default: mod.Foo }))
);This works because you’re transforming the resolved value into the { default: ... } shape that React.lazy expects. [2]
Common failure mode: returning a module without default (or with default: undefined) triggers errors like “element type is invalid” / “promise resolves to undefined,” because React.lazy can’t find a component at module.default. [3]
Sources: [1] React docs on lazy and default exports, [2] example pattern for named exports, [3] discussion of missing default export causing React.lazy errors.
🏁 Script executed:
cat -n ./frontend/preview/preview.tsx | head -50Repository: wavetermdev/waveterm
Length of output: 2697
🏁 Script executed:
cat -n ./frontend/preview/preview.tsx | sed -n '38,80p'Repository: wavetermdev/waveterm
Length of output: 2013
🏁 Script executed:
# Check if any of the preview files exist and see how they're structured
find ./frontend/preview/previews -name "*.preview.tsx" -type f | head -3Repository: wavetermdev/waveterm
Length of output: 115
🏁 Script executed:
cat -n ./frontend/preview/previews/modal-about.preview.tsx | head -30Repository: wavetermdev/waveterm
Length of output: 514
🏁 Script executed:
# Check how many preview files exist and sample a few to see export patterns
find ./frontend/preview/previews -name "*.preview.tsx" -type f | xargs -I {} bash -c 'echo "=== {} ===" && head -20 {}'Repository: wavetermdev/waveterm
Length of output: 476
Consider hardening preview module export resolution with explicit fallback.
The fallback to Object.values(mod)[0] is intentional (per the comment on line 16) to support both default and named exports, but is fragile: it's order-dependent and could select an unintended export if a file exports multiple values. Since preview files like modal-about.preview.tsx use named exports only, make the export selection explicit and fail fast with a clear error.
🔧 Suggested change
- lazy(() =>
- loader().then((mod) => ({ default: (mod.default ?? Object.values(mod)[0]) as React.ComponentType }))
- ),
+ lazy(() =>
+ loader().then((mod) => {
+ const component =
+ (mod.default as React.ComponentType | undefined) ??
+ (mod.Preview as React.ComponentType | undefined);
+ if (!component) {
+ throw new Error(`Preview module "${path}" must export default or Preview`);
+ }
+ return { default: component };
+ })
+ ),📝 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.
| Object.entries(previewModules).map(([path, loader]) => [ | |
| pathToKey(path), | |
| lazy(() => | |
| loader().then((mod) => ({ default: (mod.default ?? Object.values(mod)[0]) as React.ComponentType })) | |
| ), | |
| Object.entries(previewModules).map(([path, loader]) => [ | |
| pathToKey(path), | |
| lazy(() => | |
| loader().then((mod) => { | |
| const component = | |
| (mod.default as React.ComponentType | undefined) ?? | |
| (mod.Preview as React.ComponentType | undefined); | |
| if (!component) { | |
| throw new Error(`Preview module "${path}" must export default or Preview`); | |
| } | |
| return { default: component }; | |
| }) | |
| ), |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/preview/preview.tsx` around lines 30 - 34, The lazy loader currently
falls back to Object.values(mod)[0] which can pick the wrong export; update the
loader inside the Object.entries(previewModules).map callback (the lazy(() =>
loader().then(...)) block) to explicitly resolve the export: if mod.default
exists use it; otherwise examine the named exports (filtering out common module
metadata like __esModule), if there's exactly one remaining named export return
that as the component; otherwise throw a clear Error that includes the preview
path/key (use pathToKey(path)) and lists available export names so the failure
is explicit and actionable.
| <a | ||
| href="/" | ||
| className="flex items-center gap-1.5 text-accent text-sm hover:opacity-80 transition-opacity font-mono" | ||
| > | ||
| ← index | ||
| </a> |
There was a problem hiding this comment.
Avoid absolute “/” for index/back links.
Using href="/" assumes the preview app is served from the domain root. If this is deployed under a subpath (e.g., Pages preview), the links will break. Prefer clearing the query while keeping the current path.
🔧 Suggested change
- href="/"
+ href="?"- <a href="/" className="text-accent text-sm hover:opacity-80">
+ <a href="?" className="text-accent text-sm hover:opacity-80">Also applies to: 105-106
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/preview/preview.tsx` around lines 70 - 75, The index/back links
currently use a hardcoded href="/" which breaks when the app is served from a
subpath; update the two anchor links (the "← index" anchors at the shown
locations) to preserve the current pathname while clearing query params instead
of pointing to "/", e.g. obtain the current path from the router or
window.location.pathname and navigate to that pathname with an empty query (use
Next.js useRouter().push/replace({ pathname: router.pathname, query: {} }) or
build href from window.location.pathname) so the link works under subpaths and
only removes the query string.
adds a new "preview server" for UI testing. hooking up to cloudflare pages deployments. for now, just a test "about modal" component. will add more later.