ui-next: page registration#1159
Conversation
WalkthroughThe PR refactors the UI runtime and plugin handling: it replaces the Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/ui-next/src/registry/page.tsx (1)
18-21: ⚡ Quick winReconsider the props type signature.
The wrapper component is typed as accepting
React.PropsWithChildren<P>, but:
- It's called without children in
app.tsx(line 31:<Comp />)- Spreading
{...props}toPageCompincludes achildrenprop thatPageCompdoesn't expectThe wrapper generates its own children (
<PageComp />), so it shouldn't accept children from its caller.♻️ Proposed fix: Remove children from props type
- default(props: React.PropsWithChildren<P>) { + default(props: P) { return ( <LayoutComp> <PageComp {...props} /> </LayoutComp> ); },Alternatively, if pages should support receiving children, destructure them:
- default(props: React.PropsWithChildren<P>) { + default({ children, ...pageProps }: React.PropsWithChildren<P>) { return ( <LayoutComp> - <PageComp {...props} /> + <PageComp {...pageProps} /> </LayoutComp> ); },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui-next/src/registry/page.tsx` around lines 18 - 21, The wrapper component currently types its parameter as React.PropsWithChildren<P>, causing an unexpected children prop to be forwarded to PageComp; change the component signature from default(props: React.PropsWithChildren<P>) to default(props: P) so the wrapper does not accept or forward children, and keep using <LayoutComp><PageComp {...props} /></LayoutComp> (or alternatively explicitly destructure and drop children: const { children, ...rest } = props and pass rest to PageComp) to ensure PageComp only receives the props it expects.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/ui-next/index.ts`:
- Around line 52-55: The generated plugin entries use raw interpolation and
place name before the plugin spread so plugin${i}.name can overwrite it and
unescaped addon names can break JS; change the construction to ensure the
addon-derived name is safely JSON-escaped (e.g. use JSON.stringify(addonName)
when inserting the literal) and make the name property come after the plugin
spread so it cannot be overridden (e.g. produce an entry like `{ ...plugin${i},
name: <escaped-addonName> }` or use Object.assign({}, plugin${i}, { name:
<escaped-addonName> }) ); apply the same fix to the other occurrence around
lines 128-131.
---
Nitpick comments:
In `@packages/ui-next/src/registry/page.tsx`:
- Around line 18-21: The wrapper component currently types its parameter as
React.PropsWithChildren<P>, causing an unexpected children prop to be forwarded
to PageComp; change the component signature from default(props:
React.PropsWithChildren<P>) to default(props: P) so the wrapper does not accept
or forward children, and keep using <LayoutComp><PageComp {...props}
/></LayoutComp> (or alternatively explicitly destructure and drop children:
const { children, ...rest } = props and pass rest to PageComp) to ensure
PageComp only receives the props it expects.
🪄 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: 6818855e-0d95-4d73-a525-6a5c46c9398f
📒 Files selected for processing (13)
packages/ui-next/index.tspackages/ui-next/src/app.tsxpackages/ui-next/src/components/layout.tsxpackages/ui-next/src/globals.tspackages/ui-next/src/main.tsxpackages/ui-next/src/pages/homepage.tsxpackages/ui-next/src/pages/index.tspackages/ui-next/src/pages/problem_main.tsxpackages/ui-next/src/registry/index.tspackages/ui-next/src/registry/page.tsxpackages/ui-next/src/registry/plugin.tspackages/ui-next/src/registry/store.tspackages/ui-next/src/registry/types.ts
💤 Files with no reviewable changes (1)
- packages/ui-next/src/globals.ts
| const exports = `export default [${entries.map((e, i) => { | ||
| const addonName = path.basename(path.resolve(e, '..', '..')); | ||
| return `{ name: '${addonName}', ...plugin${i} }`; | ||
| }).join(', ')}];`; |
There was a problem hiding this comment.
Make generated plugin name deterministic and safely escaped.
On Lines 52-55 and Lines 128-131, { name: '...', ...plugin${i} } lets plugin${i}.name overwrite the addon-derived name, and raw string interpolation can break generated JS for quoted names.
💡 Suggested patch
-const exports = `export default [${entries.map((e, i) => {
- const addonName = path.basename(path.resolve(e, '..', '..'));
- return `{ name: '${addonName}', ...plugin${i} }`;
-}).join(', ')}];`;
+const exports = `export default [${entries.map((e, i) => {
+ const addonName = JSON.stringify(path.basename(path.resolve(e, '..', '..')));
+ return `{ ...plugin${i}, name: ${addonName} }`;
+}).join(', ')}];`;-`window.__hydroPlugins = [${entries.map((e, i) => {
- const addonName = path.basename(path.resolve(e, '..', '..'));
- return `{ name: '${addonName}', ...plugin${i} }`;
-}).join(', ')}];`,
+`window.__hydroPlugins = [${entries.map((e, i) => {
+ const addonName = JSON.stringify(path.basename(path.resolve(e, '..', '..')));
+ return `{ ...plugin${i}, name: ${addonName} }`;
+}).join(', ')}];`,Also applies to: 128-131
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui-next/index.ts` around lines 52 - 55, The generated plugin entries
use raw interpolation and place name before the plugin spread so plugin${i}.name
can overwrite it and unescaped addon names can break JS; change the construction
to ensure the addon-derived name is safely JSON-escaped (e.g. use
JSON.stringify(addonName) when inserting the literal) and make the name property
come after the plugin spread so it cannot be overridden (e.g. produce an entry
like `{ ...plugin${i}, name: <escaped-addonName> }` or use Object.assign({},
plugin${i}, { name: <escaped-addonName> }) ); apply the same fix to the other
occurrence around lines 128-131.
There was a problem hiding this comment.
This expected behaviour.
There was a problem hiding this comment.
@renbaoshuo, understood — the placement of name before the plugin spread is intentional so that individual plugins can override the addon-derived name. I'll note this for future reviews.
( `∪。∪。) ~~
✏️ Learnings added
Learnt from: renbaoshuo
Repo: hydro-dev/Hydro PR: 1159
File: packages/ui-next/index.ts:52-55
Timestamp: 2026-05-10T15:27:48.125Z
Learning: In `packages/ui-next/index.ts`, the virtual plugin module generates entries as `{ name: '<addonName>', ...plugin${i} }` (name first, spread second) on purpose: this allows individual plugin modules to override the addon-derived `name` property by exporting their own `name`. The same pattern applies to the `buildPlugins()` esbuild bundle (`window.__hydroPlugins`). Do not flag this ordering as a bug.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/ui-next/index.ts (1)
152-152:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFix the entries count in the log message.
The
entriesvariable is aRecord<string, string>object, which does not have a.lengthproperty. This will logundefinedfor the entry count.📊 Proposed fix
- logger.success('Plugins built in %dms (%d entries, %s)', Date.now() - start, entries.length, size(totalSize)); + logger.success('Plugins built in %dms (%d entries, %s)', Date.now() - start, Object.keys(entries).length, size(totalSize));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui-next/index.ts` at line 152, The log uses entries.length but entries is a Record<string,string>, so replace the length access with a proper key count (e.g., compute Object.keys(entries).length or Object.entries(entries).length) before calling logger.success in the same location where entries and totalSize are used; update the logger.success call to pass that computed count (refer to the variables entries, totalSize and the logger.success invocation).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/ui-next/index.ts`:
- Around line 194-198: The non-dev renderer is using context.handler.user while
the dev renderer and the RendererContext interface expose the user as
context.UserContext; update the non-dev renderer to pass the same property by
replacing usages of context.handler.user with context.UserContext (and keep
UiContext as context.handler.UiContext) so both renderers consistently use
context.UserContext for the UserContext argument.
---
Outside diff comments:
In `@packages/ui-next/index.ts`:
- Line 152: The log uses entries.length but entries is a Record<string,string>,
so replace the length access with a proper key count (e.g., compute
Object.keys(entries).length or Object.entries(entries).length) before calling
logger.success in the same location where entries and totalSize are used; update
the logger.success call to pass that computed count (refer to the variables
entries, totalSize and the logger.success invocation).
🪄 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: 06b4b7a9-1bf2-4186-a4ad-6175bd864f80
📒 Files selected for processing (8)
build/prepare.jspackages/ui-next-plugin-sample/package.jsonpackages/ui-next-plugin-sample/ui/before.tsxpackages/ui-next-plugin-sample/ui/index.tspackages/ui-next-plugin-sample/ui/index.tsxpackages/ui-next/index.tspackages/ui-next/src/context/page-data.tsxpackages/ui-next/vite.config.ts
💤 Files with no reviewable changes (3)
- packages/ui-next-plugin-sample/ui/before.tsx
- packages/ui-next-plugin-sample/ui/index.ts
- build/prepare.js
✅ Files skipped from review due to trivial changes (2)
- packages/ui-next-plugin-sample/package.json
- packages/ui-next-plugin-sample/ui/index.tsx
| args: { | ||
| UserContext: context.UserContext, | ||
| UiContext: context.handler.UiContext, | ||
| ...args, | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Search for UserContext usage patterns to verify consistency
# Search for context.UserContext usage
echo "=== Searching for context.UserContext usage ==="
rg -n "context\.UserContext" --type=ts --type=js -C2
# Search for context.handler.user usage
echo -e "\n=== Searching for context.handler.user usage ==="
rg -n "context\.handler\.user" --type=ts --type=js -C2
# Search for UserContext field definitions
echo -e "\n=== Searching for UserContext field definitions ==="
rg -n "UserContext\s*:" --type=ts --type=js -C2Repository: hydro-dev/Hydro
Length of output: 3699
🏁 Script executed:
# Check the file structure around the two renderer calls to understand context origin
head -n 250 packages/ui-next/index.ts | tail -n 80Repository: hydro-dev/Hydro
Length of output: 3666
🏁 Script executed:
# Search for where these context objects come from
rg -n "export.*render|function.*render" packages/ui-next/index.ts -B5 -A10 --type=tsRepository: hydro-dev/Hydro
Length of output: 41
🏁 Script executed:
# Check if context.UserContext is set anywhere
rg -n "\.UserContext\s*=" packages/ui-next/ -B3 -A3 --type=tsRepository: hydro-dev/Hydro
Length of output: 41
Use context.UserContext consistently in both dev and non-dev renderers.
Line 195 accesses context.UserContext (the direct context property), but line 228 accesses context.handler.user. According to the RendererContext interface, UserContext is defined as a direct property of the context, not a handler property. Both reference the same value, but the non-dev renderer should use context.UserContext to match the dev renderer and align with the context interface definition.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui-next/index.ts` around lines 194 - 198, The non-dev renderer is
using context.handler.user while the dev renderer and the RendererContext
interface expose the user as context.UserContext; update the non-dev renderer to
pass the same property by replacing usages of context.handler.user with
context.UserContext (and keep UiContext as context.handler.UiContext) so both
renderers consistently use context.UserContext for the UserContext argument.
Summary by CodeRabbit