give :section back — the splat took it from fifteen plugins
`/<route>` and `/<route>/:section` are what every plugin has always had, and fifteen of them read useParams().section. Replacing the pair with a splat took that parameter away from all of them at once: the screens still mounted and every one lost its selection, which is worse than a 404 because nothing says so. The splat stays, for depth. React-router ranks a static or dynamic segment above it, so a plugin with one segment gets `section` again and only deeper paths fall through. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -101,24 +101,31 @@ export function App() {
|
|||||||
{/* One route per SCREEN, not per plugin — `screenId` is the key, because a plugin with two
|
{/* One route per SCREEN, not per plugin — `screenId` is the key, because a plugin with two
|
||||||
screens (caldav: Calendar and Contacts) contributes two entries sharing one `appName`. */}
|
screens (caldav: Calendar and Contacts) contributes two entries sharing one `appName`. */}
|
||||||
{/*
|
{/*
|
||||||
A SPLAT, so the whole subtree is the plugin's.
|
Three routes, and the third is the only new one.
|
||||||
|
|
||||||
It was a pair — the bare route and `/:section` — which gave a plugin exactly one segment and
|
`/<route>` and `/<route>/:section` are what every plugin has always had, and fifteen of
|
||||||
made the platform the author of what it meant. Anything deeper had to go in the query
|
them read `useParams().section`. A splat ALONE replaced that for a day and took the
|
||||||
string, and `/fofos-admin/layout/header` was not expressible at all.
|
parameter away from all of them at once — the screens still mounted and every one of them
|
||||||
|
lost its selection, which is a worse failure than a 404 because nothing says so.
|
||||||
|
|
||||||
A splat matches zero segments too, so this is one route where there were two, and the
|
The splat is added for depth rather than instead of them: `/fofos-admin/layout/header` was
|
||||||
plugin reads its own position off the location. Routing inside a plugin is the plugin's,
|
not expressible before, and a plugin that wants its own subtree reads the location.
|
||||||
which is the same line `mountPrefix` already draws around its API: the platform owns where
|
React-router ranks a static or dynamic segment above a splat, so a plugin with one segment
|
||||||
a plugin STARTS and nothing after it.
|
still gets `section` and only deeper paths fall through.
|
||||||
*/}
|
*/}
|
||||||
{installedPlugins.map((plugin) => (
|
{installedPlugins.flatMap((plugin) => [
|
||||||
|
<Route key={plugin.screenId} path={plugin.route} element={<Dashboard.PluginScreen {...plugin} />} />,
|
||||||
<Route
|
<Route
|
||||||
key={plugin.screenId}
|
key={`${plugin.screenId}-section`}
|
||||||
|
path={`${plugin.route}/:section`}
|
||||||
|
element={<Dashboard.PluginScreen {...plugin} />}
|
||||||
|
/>,
|
||||||
|
<Route
|
||||||
|
key={`${plugin.screenId}-deep`}
|
||||||
path={`${plugin.route}/*`}
|
path={`${plugin.route}/*`}
|
||||||
element={<Dashboard.PluginScreen {...plugin} />}
|
element={<Dashboard.PluginScreen {...plugin} />}
|
||||||
/>
|
/>,
|
||||||
))}
|
])}
|
||||||
<Route path="/system-monitor" element={<Dashboard.SystemMonitorScreen />} />
|
<Route path="/system-monitor" element={<Dashboard.SystemMonitorScreen />} />
|
||||||
<Route path="/system-monitor/:scope" element={<Dashboard.SystemMonitorScreen />} />
|
<Route path="/system-monitor/:scope" element={<Dashboard.SystemMonitorScreen />} />
|
||||||
{/* TEMPORARY / EXPERIMENTAL — offline file transfer over animated QR codes */}
|
{/* TEMPORARY / EXPERIMENTAL — offline file transfer over animated QR codes */}
|
||||||
|
|||||||
Reference in New Issue
Block a user