From 5ebd1a14a071d4a15e02062772a3eb02e6ed05a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 7 May 2026 14:03:07 +0200 Subject: [PATCH] plugin-app: fix param substitution to use word boundary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use a word-boundary regex (:name\b) instead of a plain string replace so that a shorter param like :a doesn't corrupt a longer param :ab when both are present in the route. Signed-off-by: Patrik Oldsberg Signed-off-by: Fredrik Adelöw Co-authored-by: Cursor --- plugins/app/src/extensions/AppRoutes.test.tsx | 47 +++++++++++++++++++ plugins/app/src/extensions/AppRoutes.tsx | 7 ++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/plugins/app/src/extensions/AppRoutes.test.tsx b/plugins/app/src/extensions/AppRoutes.test.tsx index f8ce09a155..83b0c596b4 100644 --- a/plugins/app/src/extensions/AppRoutes.test.tsx +++ b/plugins/app/src/extensions/AppRoutes.test.tsx @@ -539,6 +539,53 @@ describe('AppRoutes', () => { }); }); + it('should not corrupt a longer param when a shorter param is a prefix of it', async () => { + const LocationDisplay = () => { + const location = useLocation(); + return
{location.pathname}
; + }; + + const targetPage = PageBlueprint.make({ + name: 'target', + params: { + path: '/target/:ab/:a', + loader: async () => ( +
+ Target Page + +
+ ), + }, + }); + + renderTestApp({ + extensions: [targetPage], + initialRouteEntries: ['/source/bar/foo'], + config: { + ...DEFAULT_CONFIG, + app: { + ...DEFAULT_CONFIG.app, + extensions: [ + { + 'app/routes': { + config: { + redirects: [{ from: '/source/:ab/:a', to: '/target/:ab/:a' }], + }, + }, + }, + ], + }, + }, + }); + + await waitFor(() => { + expect(screen.getByText('Target Page')).toBeInTheDocument(); + expect(screen.getByTestId('location')).toHaveTextContent( + '/target/bar/foo', + ); + }); + }); + it('should not interfere with normal routes when redirects are configured', async () => { const homePage = PageBlueprint.make({ name: 'home', diff --git a/plugins/app/src/extensions/AppRoutes.tsx b/plugins/app/src/extensions/AppRoutes.tsx index 8dd05aaa5a..04d94ebc35 100644 --- a/plugins/app/src/extensions/AppRoutes.tsx +++ b/plugins/app/src/extensions/AppRoutes.tsx @@ -27,7 +27,12 @@ function RedirectWithParams({ to }: { to: string }) { const params = useParams() as Record; let target = to; for (const [name, value] of Object.entries(params)) { - target = target.replaceAll(name === '*' ? '*' : `:${name}`, value ?? ''); + // Use \b (word boundary) for named params so that `:a` doesn't + // accidentally match inside `:ab` when both are present. + target = target.replace( + name === '*' ? /\*/g : new RegExp(`:${name}\\b`, 'g'), + value ?? '', + ); } return ; }