From 705158395339892b1680e3330f49f6eac8a945db Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Wed, 15 Apr 2026 11:38:55 -0600 Subject: [PATCH 1/2] feat(application-starter): inject migration guide URLs into final prompt When the user brief indicates a Next.js or Remix/React Router migration, append an instruction telling the downstream agent to fetch the relevant tanstack.com migration guide and follow it. Keeps the guide out of the analyze stage and out of the UI; only affects the final generation prompt. --- src/components/application-builder/shared.ts | 23 ++++++++++++++++++++ src/utils/application-starter.server.ts | 10 ++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/components/application-builder/shared.ts b/src/components/application-builder/shared.ts index ca71817bc..009065cce 100644 --- a/src/components/application-builder/shared.ts +++ b/src/components/application-builder/shared.ts @@ -52,8 +52,15 @@ export type StarterPartnerButtonStyle = CSSProperties & { const nextJsMigrationPattern = /migrat(?:e|ing|ion).*(next(?:\.js)?)|(next(?:\.js)?).*(migrat(?:e|ing|ion))/i +const remixMigrationPattern = + /migrat(?:e|ing|ion).*(remix|react[\s-]?router)|(remix|react[\s-]?router).*(migrat(?:e|ing|ion))/i const migrationRepositoryUrlPattern = /^(https?:\/\/\S+|git@\S+|ssh:\/\/\S+)$/i +export const STARTER_NEXTJS_MIGRATION_GUIDE_URL = + 'https://tanstack.com/start/latest/docs/framework/react/migrate-from-next-js' +export const STARTER_REMIX_MIGRATION_GUIDE_URL = + 'https://tanstack.com/router/latest/docs/guide/how-to/migrate-from-react-router' + export const starterPinnedLibraryIds = [ 'start', 'router', @@ -142,6 +149,22 @@ export function isNextJsMigrationInput(input: string) { return nextJsMigrationPattern.test(input) } +export function isRemixMigrationInput(input: string) { + return remixMigrationPattern.test(input) +} + +export function getStarterMigrationGuideUrl(input: string) { + if (isNextJsMigrationInput(input)) { + return STARTER_NEXTJS_MIGRATION_GUIDE_URL + } + + if (isRemixMigrationInput(input)) { + return STARTER_REMIX_MIGRATION_GUIDE_URL + } + + return null +} + export function normalizeMigrationRepositoryUrl(value: string) { return value.trim() } diff --git a/src/utils/application-starter.server.ts b/src/utils/application-starter.server.ts index 13a69a873..c3a9f5e08 100644 --- a/src/utils/application-starter.server.ts +++ b/src/utils/application-starter.server.ts @@ -17,7 +17,10 @@ import { type ApplicationStarterResult, } from '~/utils/application-starter' import type { LibraryId } from '~/libraries' -import { starterAddonLibraryIds } from '~/components/application-builder/shared' +import { + getStarterMigrationGuideUrl, + starterAddonLibraryIds, +} from '~/components/application-builder/shared' import { getApplicationStarterGuidanceLines, getApplicationStarterPartnerSuggestions, @@ -400,6 +403,10 @@ function buildPromptGenerationRequest({ ].join('\n') const userBrief = getApplicationStarterUserBrief(request.input) const starterGuidanceLines = getApplicationStarterGuidanceLines(request.input) + const migrationGuideUrl = getStarterMigrationGuideUrl(request.input) + const migrationGuideInstruction = migrationGuideUrl + ? `The prompt must instruct the agent to fetch ${migrationGuideUrl} and use it as the primary reference for the migration, following its steps in order.` + : null return [ 'Write a short, natural final prompt for a stronger coding agent.', @@ -417,6 +424,7 @@ function buildPromptGenerationRequest({ 'If the user says things like make it cool, keep it minimal, or do not include something, restate those instructions explicitly in the final prompt instead of compressing them away.', 'Use the resolved starter plan as fixed input. Do not redesign the stack unless the original brief requires sequencing work after scaffolding.', 'Keep the prompt concise and plain-English. Avoid internal process language like fixed input, resolved plan, objective, implementation notes, or deliverable.', + ...(migrationGuideInstruction ? [migrationGuideInstruction] : []), '', `Context: ${request.context}`, `User request: ${userBrief}`, From 994c4de61a6920ff6f4026f272c9373c75ae342c Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Tue, 20 Oct 2026 00:52:48 -0700 Subject: [PATCH 2/2] fix: match migration guides to explicit frameworks --- src/utils/application-starter.ts | 6 ++--- tests/application-starter-partners.test.ts | 26 +++++++++++++++++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/utils/application-starter.ts b/src/utils/application-starter.ts index a171a7d7c..8791335cd 100644 --- a/src/utils/application-starter.ts +++ b/src/utils/application-starter.ts @@ -1299,7 +1299,7 @@ function buildPrompt({ '', ...(migrationGuideUrl ? [ - `Fetch ${migrationGuideUrl} and use it as the primary migration reference, following its steps in order.`, + `Fetch ${migrationGuideUrl} for the applicable routing and framework migration concepts. Keep the fresh TanStack project and the migration strategy below authoritative; do not apply in-place setup steps from the guide.`, '', ] : []), @@ -1484,9 +1484,9 @@ export function buildDownloadUrl(recipe: ApplicationStarterRecipe) { } const nextJsMigrationPattern = - /migrat(?:e|ing|ion).*(next(?:\.js)?)|(next(?:\.js)?).*(migrat(?:e|ing|ion))/i + /migrat(?:e|ing|ion)[\s\S]*\bnext(?:\.?js|\s+(?:app|application|project))\b|\bnext(?:\.?js|\s+(?:app|application|project))\b[\s\S]*migrat(?:e|ing|ion)/i const remixMigrationPattern = - /migrat(?:e|ing|ion).*(remix|react[\s-]?router)|(remix|react[\s-]?router).*(migrat(?:e|ing|ion))/i + /migrat(?:e|ing|ion)[\s\S]*\b(remix|react[\s-]?router)\b|\b(remix|react[\s-]?router)\b[\s\S]*migrat(?:e|ing|ion)/i export const STARTER_NEXTJS_MIGRATION_GUIDE_URL = 'https://tanstack.com/start/latest/docs/framework/react/migrate-from-next-js' diff --git a/tests/application-starter-partners.test.ts b/tests/application-starter-partners.test.ts index 50e1eac63..5bd61c509 100644 --- a/tests/application-starter-partners.test.ts +++ b/tests/application-starter-partners.test.ts @@ -14,6 +14,7 @@ require.extensions['.svg'] = loadAsset const { resolveApplicationStarterDeterministically, + getStarterMigrationGuideUrl, }: typeof import('../src/utils/application-starter') = require('../src/utils/application-starter') const { composeApplicationStarterInput, @@ -555,10 +556,33 @@ test('migration prompts include the matching migration guide', async () => { input: `Migrate my ${source} app to TanStack Start.`, }) assert.ok(result.prompt.includes(`Fetch ${guide}`)) + assert.ok(result.prompt.includes('do not apply in-place setup steps')) } const result = await resolveApplicationStarterDeterministically({ context: 'home', input: 'Build a dashboard.', }) - assert.equal(result.prompt.includes('primary migration reference'), false) + assert.equal(result.prompt.includes('Fetch https://tanstack.com/'), false) +}) + +test('migration guides distinguish framework names from timing and accept multiline briefs', () => { + assert.equal( + getStarterMigrationGuideUrl('Migrate my Remix app next week'), + 'https://tanstack.com/router/latest/docs/how-to/migrate-from-react-router', + ) + assert.equal(getStarterMigrationGuideUrl('Migrate my app next week'), null) + for (const input of [ + 'Migrate my app\nfrom Next.js to TanStack Start', + 'Next.js app\nneeds migration', + 'Migrate my Next app to TanStack Start', + ]) { + assert.equal( + getStarterMigrationGuideUrl(input), + 'https://tanstack.com/start/latest/docs/framework/react/migrate-from-next-js', + ) + } + assert.equal( + getStarterMigrationGuideUrl('Migrate my app\nfrom Remix to TanStack Start'), + 'https://tanstack.com/router/latest/docs/how-to/migrate-from-react-router', + ) })