Skip to content

ui-next: page registration#1159

Open
renbaoshuo wants to merge 1 commit into
hydro-dev:masterfrom
renbaoshuo:ui-next
Open

ui-next: page registration#1159
renbaoshuo wants to merge 1 commit into
hydro-dev:masterfrom
renbaoshuo:ui-next

Conversation

@renbaoshuo
Copy link
Copy Markdown
Contributor

@renbaoshuo renbaoshuo commented May 10, 2026

Summary by CodeRabbit

  • New Features
    • Introduced multi-page navigation system with homepage and problem-focused pages
    • Extended plugin system to support dynamic page registration capabilities
    • Added application-level error handling and async loading states for improved user experience
    • Updated application layout rendering with enhanced component composition support

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

Walkthrough

This PR introduces a page registration system that replaces the static page:app slot with a dynamic app:root slot that renders pages based on usePageData().name. The system includes a new registerPage API for dynamically loading and rendering pages with optional layout composition, triggered by importing ./pages at bootstrap. The registry store is updated to propagate changes to subscribers via useSyncExternalStore. Two initial pages (Homepage and ProblemMain) are registered with a bidirectional navigation link. Global configuration constants for injection state and plugin URLs are exported, and the virtual plugin module now includes addon names in its default export.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • hydro-dev/Hydro#1138: Modifies the same virtual:hydro-plugins generation and registry/slot infrastructure that this PR updates.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'ui-next: page registration' clearly and concisely describes the main change: implementing page registration functionality for the ui-next package.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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

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

🧹 Nitpick comments (1)
packages/ui-next/src/registry/page.tsx (1)

18-21: ⚡ Quick win

Reconsider the props type signature.

The wrapper component is typed as accepting React.PropsWithChildren<P>, but:

  1. It's called without children in app.tsx (line 31: <Comp />)
  2. Spreading {...props} to PageComp includes a children prop that PageComp doesn't expect

The 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8dc7a73 and ff02037.

📒 Files selected for processing (13)
  • packages/ui-next/index.ts
  • packages/ui-next/src/app.tsx
  • packages/ui-next/src/components/layout.tsx
  • packages/ui-next/src/globals.ts
  • packages/ui-next/src/main.tsx
  • packages/ui-next/src/pages/homepage.tsx
  • packages/ui-next/src/pages/index.ts
  • packages/ui-next/src/pages/problem_main.tsx
  • packages/ui-next/src/registry/index.ts
  • packages/ui-next/src/registry/page.tsx
  • packages/ui-next/src/registry/plugin.ts
  • packages/ui-next/src/registry/store.ts
  • packages/ui-next/src/registry/types.ts
💤 Files with no reviewable changes (1)
  • packages/ui-next/src/globals.ts

Comment thread packages/ui-next/index.ts
Comment on lines +52 to +55
const exports = `export default [${entries.map((e, i) => {
const addonName = path.basename(path.resolve(e, '..', '..'));
return `{ name: '${addonName}', ...plugin${i} }`;
}).join(', ')}];`;
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This expected behaviour.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

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