Conversation
5c6d4b0 to
a6124fb
Compare
commit: |
|
/review |
| // even though the loader always redirects/throws and this component will never render. | ||
| Component: () => null, | ||
| } | ||
| : undefined; |
There was a problem hiding this comment.
[1/2 — Medium] Loader-only index route semantics unclear
The new behavior creates index routes for loader-only modules/resources when source.loader exists but source.component doesn't. However, looking at the codebase, source.loader is only populated by withGuardsLoader() when guards are present.
Issue: The condition source.loader is being used as a proxy for "has guards", which is implicit and fragile. If a module/resource could have a loader without guards in the future, this would incorrectly create an index route with Component: () => null.
Recommendation: Make the intent explicit by checking for guards directly, or document this coupling clearly. Consider:
: (source.loader && hasGuards) // or add a flag like `source.hasGuards`
? {
index: true,
loader: source.loader,
Component: () => null,
}
: undefined;Alternatively, add a JSDoc comment explaining that source.loader only exists when guards are present.
There was a problem hiding this comment.
Added a JSDoc comment explaining that source.loader is currently only set by withGuardsLoader() when guards are present, and that this condition should be revisited if that coupling changes in the future.
Commit: a9d566c
|
|
||
| const guardResult = await runGuards(module.guards); | ||
| if (guardResult.type !== "pass") return null; | ||
| if (guardResult.type === "hidden") return null; |
There was a problem hiding this comment.
[2/2 — High] Navigation filtering change breaks API contract
Breaking change: The navigation filtering logic changed from "only show items where guards pass" (!== "pass") to "show everything except explicitly hidden items" (=== "hidden").
API consistency issue: This means modules/resources with redirectTo() guards now appear in navigation even though they never render their component — they always redirect away. This creates a confusing UX where users see a nav item that, when clicked, immediately redirects elsewhere.
Example of problematic behavior:
defineModule({
path: "old-dashboard",
guards: [() => redirectTo("/new-dashboard")],
resources: [/* ... */],
});
// "Old Dashboard" appears in sidebar, but clicking it redirects to "/new-dashboard"Recommendation: Consider one of these alternatives:
- Add a new guard result type like
{ type: "silent-redirect" }that redirects but hides from navigation - Add an explicit
meta.hiddenInNavflag so developers can control visibility independently - Document this new behavior clearly as intentional UX (nav items can be "redirect aliases")
The current approach conflates routing behavior (redirect) with UI visibility (sidebar), which may surprise developers.
There was a problem hiding this comment.
This is intentional — keeping redirectTo() items visible in the sidebar is a deliberate design choice for two reasons:
- Redirect-only modules: A module may have no component but should still appear as a sidebar item that redirects elsewhere (e.g. aliasing a legacy path).
- Child resource visibility: In auto-generated sidebar mode, hiding a module also hides all of its child resources from navigation. A module that redirects at its own path may still have child resources with real pages that users need to access.
If a developer wants to hide a module/resource from the sidebar entirely, they should use hidden() instead of redirectTo().
Added inline comments in navigation.tsx clarifying this design intent, and updated the changeset with a guard result → nav visibility table documenting the behavior.
Commit: 5888a43
af9f63b to
4e2ecf3
Compare
See changeset for detail