@tanstack/cli 0.60.0 → 0.61.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,209 @@
1
+ import { z } from 'zod'
2
+
3
+ const TANSTACK_API_BASE = 'https://tanstack.com/api/data'
4
+
5
+ const LibrarySchema = z.object({
6
+ id: z.string(),
7
+ name: z.string(),
8
+ tagline: z.string(),
9
+ description: z.string().optional(),
10
+ frameworks: z.array(z.string()),
11
+ latestVersion: z.string(),
12
+ latestBranch: z.string().optional(),
13
+ availableVersions: z.array(z.string()),
14
+ repo: z.string(),
15
+ docsRoot: z.string().optional(),
16
+ defaultDocs: z.string().optional(),
17
+ docsUrl: z.string().optional(),
18
+ githubUrl: z.string().optional(),
19
+ })
20
+
21
+ const LibrariesResponseSchema = z.object({
22
+ libraries: z.array(LibrarySchema),
23
+ groups: z.record(z.array(z.string())),
24
+ groupNames: z.record(z.string()),
25
+ })
26
+
27
+ const PartnerSchema = z.object({
28
+ id: z.string(),
29
+ name: z.string(),
30
+ tagline: z.string().optional(),
31
+ description: z.string(),
32
+ category: z.string(),
33
+ categoryLabel: z.string(),
34
+ libraries: z.array(z.string()),
35
+ url: z.string(),
36
+ })
37
+
38
+ const PartnersResponseSchema = z.object({
39
+ partners: z.array(PartnerSchema),
40
+ categories: z.array(z.string()),
41
+ categoryLabels: z.record(z.string()),
42
+ })
43
+
44
+ export const LIBRARY_GROUPS = ['state', 'headlessUI', 'performance', 'tooling'] as const
45
+
46
+ // Algolia config (public read-only keys)
47
+ const ALGOLIA_APP_ID = 'FQ0DQ6MA3C'
48
+ const ALGOLIA_API_KEY = '10c34d6a5c89f6048cf644d601e65172'
49
+ const ALGOLIA_INDEX = 'tanstack-test'
50
+
51
+ export type LibrariesResponse = z.infer<typeof LibrariesResponseSchema>
52
+ export type PartnersResponse = z.infer<typeof PartnersResponseSchema>
53
+
54
+ export async function fetchLibraries(): Promise<LibrariesResponse> {
55
+ const response = await fetch(`${TANSTACK_API_BASE}/libraries`)
56
+ if (!response.ok) {
57
+ throw new Error(`Failed to fetch libraries: ${response.statusText}`)
58
+ }
59
+ const data = await response.json()
60
+ return LibrariesResponseSchema.parse(data)
61
+ }
62
+
63
+ export async function fetchPartners(): Promise<PartnersResponse> {
64
+ const response = await fetch(`${TANSTACK_API_BASE}/partners`)
65
+ if (!response.ok) {
66
+ throw new Error(`Failed to fetch partners: ${response.statusText}`)
67
+ }
68
+ const data = await response.json()
69
+ return PartnersResponseSchema.parse(data)
70
+ }
71
+
72
+ export async function fetchDocContent(
73
+ repo: string,
74
+ branch: string,
75
+ filePath: string,
76
+ ): Promise<string | null> {
77
+ const url = `https://raw.githubusercontent.com/${repo}/${branch}/${filePath}`
78
+ const response = await fetch(url, {
79
+ headers: { 'User-Agent': 'tanstack-cli' },
80
+ })
81
+
82
+ if (!response.ok) {
83
+ if (response.status === 404) {
84
+ return null
85
+ }
86
+ throw new Error(`Failed to fetch doc: ${response.statusText}`)
87
+ }
88
+
89
+ return response.text()
90
+ }
91
+
92
+ export async function searchTanStackDocs({
93
+ query,
94
+ library,
95
+ framework,
96
+ limit = 10,
97
+ }: {
98
+ query: string
99
+ library?: string
100
+ framework?: string
101
+ limit?: number
102
+ }): Promise<{
103
+ query: string
104
+ totalHits: number
105
+ results: Array<{
106
+ title: string
107
+ url: string
108
+ snippet: string
109
+ library: string
110
+ breadcrumb: Array<string>
111
+ }>
112
+ }> {
113
+ const ALL_LIBRARIES = [
114
+ 'config',
115
+ 'form',
116
+ 'optimistic',
117
+ 'pacer',
118
+ 'query',
119
+ 'ranger',
120
+ 'react-charts',
121
+ 'router',
122
+ 'start',
123
+ 'store',
124
+ 'table',
125
+ 'virtual',
126
+ 'db',
127
+ 'devtools',
128
+ ]
129
+ const ALL_FRAMEWORKS = ['react', 'vue', 'solid', 'svelte', 'angular']
130
+
131
+ const filterParts: Array<string> = ['version:latest']
132
+
133
+ if (library) {
134
+ const otherLibraries = ALL_LIBRARIES.filter((l) => l !== library)
135
+ const exclusions = otherLibraries.map((l) => `NOT library:${l}`).join(' AND ')
136
+ if (exclusions) filterParts.push(`(${exclusions})`)
137
+ }
138
+
139
+ if (framework) {
140
+ const otherFrameworks = ALL_FRAMEWORKS.filter((f) => f !== framework)
141
+ const exclusions = otherFrameworks.map((f) => `NOT framework:${f}`).join(' AND ')
142
+ if (exclusions) filterParts.push(`(${exclusions})`)
143
+ }
144
+
145
+ const searchParams = {
146
+ requests: [
147
+ {
148
+ indexName: ALGOLIA_INDEX,
149
+ query,
150
+ hitsPerPage: Math.min(limit, 50),
151
+ filters: filterParts.join(' AND '),
152
+ attributesToRetrieve: ['hierarchy', 'url', 'content', 'library'],
153
+ attributesToSnippet: ['content:80'],
154
+ },
155
+ ],
156
+ }
157
+
158
+ const response = await fetch(
159
+ `https://${ALGOLIA_APP_ID}-dsn.algolia.net/1/indexes/*/queries`,
160
+ {
161
+ method: 'POST',
162
+ headers: {
163
+ 'Content-Type': 'application/json',
164
+ 'X-Algolia-Application-Id': ALGOLIA_APP_ID,
165
+ 'X-Algolia-API-Key': ALGOLIA_API_KEY,
166
+ },
167
+ body: JSON.stringify(searchParams),
168
+ },
169
+ )
170
+
171
+ if (!response.ok) {
172
+ throw new Error(`Algolia search failed: ${response.statusText}`)
173
+ }
174
+
175
+ const searchResponse = (await response.json()) as {
176
+ results: Array<{
177
+ hits: Array<{
178
+ objectID: string
179
+ url: string
180
+ library?: string
181
+ hierarchy: Record<string, string | undefined>
182
+ content?: string
183
+ _snippetResult?: { content?: { value?: string } }
184
+ }>
185
+ nbHits?: number
186
+ }>
187
+ }
188
+
189
+ const searchResult = searchResponse.results[0]
190
+
191
+ const results = searchResult.hits.map((hit) => {
192
+ const breadcrumb = Object.values(hit.hierarchy).filter(
193
+ (v): v is string => Boolean(v),
194
+ )
195
+ return {
196
+ title: hit.hierarchy.lvl1 || hit.hierarchy.lvl0 || 'Untitled',
197
+ url: hit.url,
198
+ snippet: hit._snippetResult?.content?.value || hit.content || '',
199
+ library: hit.library || 'unknown',
200
+ breadcrumb,
201
+ }
202
+ })
203
+
204
+ return {
205
+ query,
206
+ totalHits: searchResult.nbHits || results.length,
207
+ results,
208
+ }
209
+ }
package/src/options.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  finalizeAddOns,
5
5
  getFrameworkById,
6
6
  getPackageManager,
7
+ loadStarter,
7
8
  populateAddOnOptionsDefaults,
8
9
  readConfigFile,
9
10
  } from '@tanstack/create'
@@ -17,8 +18,13 @@ import {
17
18
  selectExamples,
18
19
  selectGit,
19
20
  selectPackageManager,
21
+ selectTemplate,
20
22
  selectToolchain,
21
23
  } from './ui-prompts.js'
24
+ import {
25
+ listTemplateChoices,
26
+ resolveStarterSpecifier,
27
+ } from './command-line.js'
22
28
 
23
29
  import {
24
30
  getCurrentDirectoryName,
@@ -72,6 +78,39 @@ export async function promptForCreateOptions(
72
78
  !!cliOptions.routerOnly ||
73
79
  (isLegacyTemplate ? template !== 'file-router' : false)
74
80
 
81
+ if (!cliOptions.starter) {
82
+ if (cliOptions.template && !isLegacyTemplate) {
83
+ cliOptions.starter = cliOptions.template
84
+ } else if (cliOptions.templateId) {
85
+ cliOptions.starter = cliOptions.templateId
86
+ }
87
+ }
88
+
89
+ if (!routerOnly && !cliOptions.starter) {
90
+ const starterChoices = await listTemplateChoices(options.framework.id)
91
+ const selectedTemplateId = await selectTemplate(
92
+ starterChoices.map((choice) => ({
93
+ id: choice.id,
94
+ name: choice.name,
95
+ description: choice.description,
96
+ })),
97
+ )
98
+ if (selectedTemplateId) {
99
+ cliOptions.starter = selectedTemplateId
100
+ }
101
+ }
102
+
103
+ const starter = !routerOnly && cliOptions.starter
104
+ ? await loadStarter(
105
+ await resolveStarterSpecifier(cliOptions.starter, options.framework.id),
106
+ )
107
+ : undefined
108
+
109
+ if (starter) {
110
+ options.framework = getFrameworkById(starter.framework) || options.framework
111
+ options.mode = starter.mode
112
+ }
113
+
75
114
  // TypeScript is always enabled with file-router
76
115
  options.typescript = true
77
116
 
@@ -114,6 +153,9 @@ export async function promptForCreateOptions(
114
153
  }
115
154
 
116
155
  if (!routerOnly) {
156
+ for (const addOn of starter?.dependsOn || []) {
157
+ addOns.add(addOn)
158
+ }
117
159
  for (const addOn of forcedAddOns) {
118
160
  addOns.add(addOn)
119
161
  }
@@ -188,6 +230,10 @@ export async function promptForCreateOptions(
188
230
  options.install = false
189
231
  }
190
232
 
233
+ if (starter) {
234
+ options.starter = starter
235
+ }
236
+
191
237
  return options
192
238
  }
193
239
 
package/src/types.ts CHANGED
@@ -10,8 +10,7 @@ export interface CliOptions {
10
10
  addOns?: Array<string> | boolean
11
11
  listAddOns?: boolean
12
12
  addonDetails?: string
13
- mcp?: boolean
14
- mcpSse?: boolean
13
+ json?: boolean
15
14
  starter?: string
16
15
  templateId?: string
17
16
  targetDir?: string
package/src/ui-prompts.ts CHANGED
@@ -60,6 +60,38 @@ export async function selectPackageManager(): Promise<PackageManager> {
60
60
  return packageManager
61
61
  }
62
62
 
63
+ export async function selectTemplate(
64
+ templates: Array<{ id: string; name: string; description?: string }>,
65
+ ): Promise<string | undefined> {
66
+ if (templates.length === 0) {
67
+ return undefined
68
+ }
69
+
70
+ const selected = await select({
71
+ message: 'Would you like to start from a template?',
72
+ options: [
73
+ {
74
+ value: undefined,
75
+ label: 'None (base starter)',
76
+ hint: 'Two-page baseline (Home + About)',
77
+ },
78
+ ...templates.map((template) => ({
79
+ value: template.id,
80
+ label: template.name,
81
+ hint: template.description,
82
+ })),
83
+ ],
84
+ initialValue: undefined,
85
+ })
86
+
87
+ if (isCancel(selected)) {
88
+ cancel('Operation cancelled.')
89
+ process.exit(0)
90
+ }
91
+
92
+ return selected
93
+ }
94
+
63
95
  // Track if we've shown the multiselect help text
64
96
  let hasShownMultiselectHelp = false
65
97
 
@@ -340,6 +340,87 @@ describe('normalizeOptions', () => {
340
340
  }
341
341
  })
342
342
 
343
+ it('prefers framework-matching template ids from registry', async () => {
344
+ __testRegisterFramework({
345
+ id: 'react',
346
+ name: 'React',
347
+ getAddOns: () => [],
348
+ supportedModes: {
349
+ 'file-router': {
350
+ displayName: 'File Router',
351
+ description: 'TanStack Router using files to define the routes',
352
+ forceTypescript: true,
353
+ },
354
+ },
355
+ })
356
+ __testRegisterFramework({
357
+ id: 'solid',
358
+ name: 'Solid',
359
+ getAddOns: () => [],
360
+ supportedModes: {
361
+ 'file-router': {
362
+ displayName: 'File Router',
363
+ description: 'TanStack Router using files to define the routes',
364
+ forceTypescript: true,
365
+ },
366
+ },
367
+ })
368
+
369
+ const originalRegistry = process.env.CTA_REGISTRY
370
+ process.env.CTA_REGISTRY = 'https://registry.example/registry.json'
371
+
372
+ fetch
373
+ .mockResponseOnce(
374
+ JSON.stringify({
375
+ templates: [
376
+ {
377
+ name: 'Blog',
378
+ description: 'React blog template',
379
+ url: './react/blog/template.json',
380
+ mode: 'file-router',
381
+ framework: 'react',
382
+ },
383
+ {
384
+ name: 'Blog',
385
+ description: 'Solid blog template',
386
+ url: './solid/blog/template.json',
387
+ mode: 'file-router',
388
+ framework: 'solid',
389
+ },
390
+ ],
391
+ }),
392
+ )
393
+ .mockResponseOnce(
394
+ JSON.stringify({
395
+ id: 'blog',
396
+ typescript: true,
397
+ framework: 'solid',
398
+ mode: 'file-router',
399
+ type: 'starter',
400
+ description: 'Solid blog template',
401
+ name: 'Blog',
402
+ dependsOn: [],
403
+ files: {},
404
+ deletedFiles: [],
405
+ }),
406
+ )
407
+
408
+ try {
409
+ const options = await normalizeOptions({
410
+ projectName: 'test',
411
+ framework: 'solid',
412
+ template: 'blog',
413
+ })
414
+
415
+ expect(options?.framework?.id).toBe('solid')
416
+ expect(options?.starter?.id).toBe(
417
+ 'https://registry.example/solid/blog/template.json',
418
+ )
419
+ } finally {
420
+ process.env.CTA_REGISTRY = originalRegistry
421
+ }
422
+ })
423
+
343
424
  it('should default to react if no framework is provided', async () => {
344
425
  __testRegisterFramework({
345
426
  id: 'react',
@@ -5,14 +5,17 @@ import {
5
5
  __testClearFrameworks,
6
6
  __testRegisterFramework,
7
7
  } from '@tanstack/create'
8
+ import * as create from '@tanstack/create'
8
9
 
9
10
  import * as prompts from '../src/ui-prompts'
11
+ import * as commandLine from '../src/command-line'
10
12
 
11
13
  import type { Framework } from '@tanstack/create'
12
14
 
13
15
  import type { CliOptions } from '../src/types'
14
16
 
15
17
  vi.mock('../src/ui-prompts')
18
+ vi.mock('../src/command-line')
16
19
 
17
20
  beforeEach(() => {
18
21
  __testClearFrameworks()
@@ -61,7 +64,31 @@ const baseCliOptions: CliOptions = {
61
64
  }
62
65
 
63
66
  function setBasicSpies() {
67
+ vi.spyOn(commandLine, 'listTemplateChoices').mockImplementation(async () => [])
68
+ vi
69
+ .spyOn(commandLine, 'resolveStarterSpecifier')
70
+ .mockImplementation(async (value) =>
71
+ value === 'blog'
72
+ ? 'https://example.com/react/blog/starter.json'
73
+ : value,
74
+ )
75
+ vi.spyOn(create, 'loadStarter').mockImplementation(
76
+ async (id) =>
77
+ ({
78
+ id: String(id),
79
+ name: 'Blog',
80
+ description: 'Blog template',
81
+ type: 'starter',
82
+ framework: 'react',
83
+ mode: 'file-router',
84
+ typescript: true,
85
+ dependsOn: [],
86
+ files: {},
87
+ deletedFiles: [],
88
+ }) as any,
89
+ )
64
90
  vi.spyOn(prompts, 'getProjectName').mockImplementation(async () => 'hello')
91
+ vi.spyOn(prompts, 'selectTemplate').mockImplementation(async () => undefined)
65
92
  vi.spyOn(prompts, 'selectPackageManager').mockImplementation(
66
93
  async () => 'npm',
67
94
  )
@@ -111,6 +138,44 @@ describe('promptForCreateOptions', () => {
111
138
  expect(options?.tailwind).toBe(true)
112
139
  })
113
140
 
141
+ it('prompts for templates when none was provided', async () => {
142
+ setBasicSpies()
143
+ vi.spyOn(commandLine, 'listTemplateChoices').mockImplementation(async () => [
144
+ {
145
+ id: 'blog',
146
+ name: 'Blog',
147
+ description: 'Blog template',
148
+ framework: 'react',
149
+ },
150
+ ])
151
+
152
+ await promptForCreateOptions(baseCliOptions, {})
153
+
154
+ expect(prompts.selectTemplate).toHaveBeenCalledWith([
155
+ {
156
+ id: 'blog',
157
+ name: 'Blog',
158
+ description: 'Blog template',
159
+ },
160
+ ])
161
+ })
162
+
163
+ it('skips template prompt when template was provided via CLI', async () => {
164
+ setBasicSpies()
165
+
166
+ await promptForCreateOptions({ ...baseCliOptions, template: 'blog' }, {})
167
+
168
+ expect(prompts.selectTemplate).not.toHaveBeenCalled()
169
+ })
170
+
171
+ it('skips template prompt in router-only mode', async () => {
172
+ setBasicSpies()
173
+
174
+ await promptForCreateOptions({ ...baseCliOptions, routerOnly: true }, {})
175
+
176
+ expect(prompts.selectTemplate).not.toHaveBeenCalled()
177
+ })
178
+
114
179
  //// Package manager
115
180
 
116
181
  it('uses the package manager from the cli options', async () => {
@@ -7,6 +7,7 @@ import {
7
7
  selectAddOns,
8
8
  selectGit,
9
9
  selectPackageManager,
10
+ selectTemplate,
10
11
  selectToolchain,
11
12
  } from '../src/ui-prompts'
12
13
 
@@ -54,6 +55,33 @@ describe('selectPackageManager', () => {
54
55
  })
55
56
  })
56
57
 
58
+ describe('selectTemplate', () => {
59
+ it('should select a template id', async () => {
60
+ vi.spyOn(clack, 'select').mockImplementation(async () => 'blog')
61
+ vi.spyOn(clack, 'isCancel').mockImplementation(() => false)
62
+
63
+ const selectedTemplate = await selectTemplate([
64
+ { id: 'blog', name: 'Blog', description: 'A blog template' },
65
+ ])
66
+
67
+ expect(selectedTemplate).toBe('blog')
68
+ })
69
+
70
+ it('should return undefined when no templates are available', async () => {
71
+ const selectedTemplate = await selectTemplate([])
72
+ expect(selectedTemplate).toBeUndefined()
73
+ })
74
+
75
+ it('should exit on cancel', async () => {
76
+ vi.spyOn(clack, 'select').mockImplementation(async () => Symbol.for('cancel'))
77
+ vi.spyOn(clack, 'isCancel').mockImplementation(() => true)
78
+
79
+ await expect(
80
+ selectTemplate([{ id: 'blog', name: 'Blog' }]),
81
+ ).rejects.toThrowError(/exit/)
82
+ })
83
+ })
84
+
57
85
  describe('selectAddOns', () => {
58
86
  it('should show keyboard shortcuts help and select add-ons', async () => {
59
87
  const noteSpy = vi.spyOn(clack, 'note').mockImplementation(() => {})
@@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test'
2
2
 
3
3
  import { attachRuntimeGuards, createReactAppFixture } from './helpers'
4
4
 
5
- test('@blocking creates a React app and navigates core demo routes', async ({ page }) => {
5
+ test('@blocking creates a React app and navigates core starter routes', async ({ page }) => {
6
6
  const fixture = await createReactAppFixture({
7
7
  appName: 'react-smoke-app',
8
8
  })
@@ -12,21 +12,21 @@ test('@blocking creates a React app and navigates core demo routes', async ({ pa
12
12
  await page.goto(fixture.url)
13
13
  await expect(
14
14
  page.getByRole('heading', {
15
- name: 'Island hours, but for product teams.',
15
+ name: 'Start simple, ship quickly.',
16
16
  }),
17
17
  ).toBeVisible()
18
18
 
19
- await page.getByRole('link', { name: 'Blog' }).click()
20
- await expect(page).toHaveURL(/\/blog\/?$/)
21
- await expect(page.getByRole('heading', { name: 'Blog' })).toBeVisible()
22
-
23
- await page.locator('main article a').first().click()
24
- await expect(page).toHaveURL(/\/blog\/.+/)
25
- await expect(page.getByText('Post', { exact: true })).toBeVisible()
26
-
27
- await page.getByRole('link', { name: 'About' }).click()
19
+ await page.getByRole('link', { name: 'About', exact: true }).click()
28
20
  await expect(page).toHaveURL(/\/about\/?$/)
29
- await expect(page.getByRole('heading', { name: 'Built for shipping fast.' })).toBeVisible()
21
+ await expect(
22
+ page.getByRole('heading', { name: 'A small starter with room to grow.' }),
23
+ ).toBeVisible()
24
+
25
+ await page.getByRole('link', { name: 'Home' }).click()
26
+ await expect(page).toHaveURL(/\/?$/)
27
+ await expect(
28
+ page.getByRole('heading', { name: 'Start simple, ship quickly.' }),
29
+ ).toBeVisible()
30
30
  } finally {
31
31
  try {
32
32
  guards.assertClean()
@@ -14,7 +14,7 @@ test('@blocking creates a React router-only app and navigates every internal lin
14
14
  try {
15
15
  await page.goto(fixture.url)
16
16
  await expect(
17
- page.getByRole('heading', { name: 'Island hours, but for product teams.' }),
17
+ page.getByRole('heading', { name: 'Start simple, ship quickly.' }),
18
18
  ).toBeVisible()
19
19
 
20
20
  const homeLinks = await page
@@ -25,36 +25,22 @@ test('@blocking creates a React router-only app and navigates every internal lin
25
25
  .sort(),
26
26
  )
27
27
 
28
- expect(homeLinks).toContain('/blog')
28
+ expect(homeLinks).toContain('/about')
29
29
 
30
- await page.locator('a[href="/blog"]').first().click()
31
- await expect(page).toHaveURL(/\/blog\/?$/)
32
- await expect(page.getByRole('heading', { name: 'Blog' })).toBeVisible()
33
-
34
- const blogPostLinks = await page
35
- .locator('a[href^="/blog/"]')
36
- .evaluateAll((anchors) =>
37
- Array.from(new Set(anchors.map((anchor) => anchor.getAttribute('href') || '')))
38
- .filter(Boolean)
39
- .sort(),
40
- )
41
-
42
- expect(blogPostLinks.length).toBeGreaterThan(0)
43
-
44
- for (const postPath of blogPostLinks) {
45
- await page.locator(`a[href="${postPath}"]`).first().click()
46
- await expect(page).toHaveURL(new RegExp(`${postPath}/?$`))
47
- await expect(page.locator('h1').first()).toBeVisible()
48
- await page.goBack()
49
- await expect(page).toHaveURL(/\/blog\/?$/)
50
- }
30
+ await page.locator('a[href="/about"]').first().click()
31
+ await expect(page).toHaveURL(/\/about\/?$/)
32
+ await expect(
33
+ page.getByRole('heading', { name: 'A small starter with room to grow.' }),
34
+ ).toBeVisible()
51
35
 
52
36
  await page.goto(`${fixture.url}/about`)
53
- await expect(page.getByRole('heading', { name: 'Built for shipping fast.' })).toBeVisible()
37
+ await expect(
38
+ page.getByRole('heading', { name: 'A small starter with room to grow.' }),
39
+ ).toBeVisible()
54
40
 
55
41
  await page.goto(fixture.url)
56
42
  await expect(
57
- page.getByRole('heading', { name: 'Island hours, but for product teams.' }),
43
+ page.getByRole('heading', { name: 'Start simple, ship quickly.' }),
58
44
  ).toBeVisible()
59
45
  } finally {
60
46
  try {
@@ -11,8 +11,9 @@ test('@blocking creates a Solid app and renders the home route', async ({ page }
11
11
 
12
12
  try {
13
13
  await page.goto(fixture.url)
14
- await expect(page.getByRole('heading', { name: /TANSTACK/i })).toBeVisible()
15
- await expect(page.getByText('The framework for next generation AI applications')).toBeVisible()
14
+ await expect(
15
+ page.getByRole('heading', { name: 'Start simple, ship quickly.' }),
16
+ ).toBeVisible()
16
17
  } finally {
17
18
  try {
18
19
  guards.assertClean()