Skip to content

Commit 4828f1a

Browse files
committed
improvement(models): chip links use the outline variant; tighten SEO checks
- Model page chip links use ChipLink's outline variant instead of restyling it - ComparisonLinks gets its own component folder - One E2E check covers every model page's search title budget and canonical; a fixture test covers the search length rules
1 parent eb14b8e commit 4828f1a

7 files changed

Lines changed: 79 additions & 64 deletions

File tree

‎apps/sim/app/(landing)/models/(shell)/[provider]/[model]/page.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ export default async function ModelPage({
198198
<ChipLink variant='primary' href='/'>
199199
Build with this model
200200
</ChipLink>
201-
<ChipLink href={provider.href} className='border border-[var(--border-1)]'>
201+
<ChipLink href={provider.href} variant='outline'>
202202
All {provider.name} models
203203
</ChipLink>
204204
<ShareButton url={`${baseUrl}${model.href}`} title={model.displayName} />

‎apps/sim/app/(landing)/models/(shell)/compare/[pair]/page.tsx‎

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,10 @@ export default async function ModelComparisonPage({
223223
<ChipLink variant='primary' href='/'>
224224
Build with these models
225225
</ChipLink>
226-
<ChipLink href={a.model.href} className='border border-[var(--border-1)]'>
226+
<ChipLink href={a.model.href} variant='outline'>
227227
{a.model.displayName} details
228228
</ChipLink>
229-
<ChipLink href={b.model.href} className='border border-[var(--border-1)]'>
229+
<ChipLink href={b.model.href} variant='outline'>
230230
{b.model.displayName} details
231231
</ChipLink>
232232
</div>
@@ -273,22 +273,19 @@ export default async function ModelComparisonPage({
273273
href={AGENT_BLOCK_DOCS_URL}
274274
target='_blank'
275275
rel='noopener noreferrer'
276-
className='border border-[var(--border-1)]'
276+
variant='outline'
277277
>
278278
Agent block docs
279279
</ChipLink>
280-
<ChipLink href={a.provider.href} className='border border-[var(--border-1)]'>
280+
<ChipLink href={a.provider.href} variant='outline'>
281281
All {a.provider.name} models
282282
</ChipLink>
283283
{b.provider.id !== a.provider.id ? (
284-
<ChipLink href={b.provider.href} className='border border-[var(--border-1)]'>
284+
<ChipLink href={b.provider.href} variant='outline'>
285285
All {b.provider.name} models
286286
</ChipLink>
287287
) : null}
288-
<ChipLink
289-
href='/models/context-windows'
290-
className='border border-[var(--border-1)]'
291-
>
288+
<ChipLink href='/models/context-windows' variant='outline'>
292289
Context window comparison
293290
</ChipLink>
294291
</div>

apps/sim/app/(landing)/models/components/comparison-links.tsx renamed to apps/sim/app/(landing)/models/components/comparison-links/comparison-links.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,7 @@ export function ComparisonLinks({ comparisons }: ComparisonLinksProps) {
99
return (
1010
<div className='flex flex-wrap gap-2'>
1111
{comparisons.map((comparison) => (
12-
<ChipLink
13-
key={comparison.slug}
14-
href={comparison.href}
15-
className='border border-[var(--border-1)]'
16-
>
12+
<ChipLink key={comparison.slug} href={comparison.href} variant='outline'>
1713
{comparison.title}
1814
</ChipLink>
1915
))}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { ComparisonLinks } from './comparison-links'

‎apps/sim/lib/tokenization/estimators.ts‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ import type { ProviderTokenizationConfig, TokenEstimate } from '@/lib/tokenizati
1212

1313
const logger = createLogger('TokenizationEstimators')
1414

15-
/**
16-
* Gets tokenization configuration for a specific provider
17-
*/
1815
function getProviderConfig(providerId: string): ProviderTokenizationConfig {
1916
const config =
2017
TOKENIZATION_CONFIG.providers[providerId as keyof typeof TOKENIZATION_CONFIG.providers]

‎apps/sim/scripts/test-landing-seo-e2e.ts‎

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ async function run() {
199199
firstPath(/^\/integrations\/[^/]+$/),
200200
firstPath(/^\/models\/anthropic\/[^/]+$/),
201201
'/models/context-windows',
202+
'/models/pricing-calculator',
203+
'/models/token-counter',
202204
firstPath(/^\/models\/compare\/[^/]+$/),
203205
]
204206
const failures: string[] = []
@@ -240,38 +242,58 @@ async function run() {
240242
}
241243
)
242244

243-
await check('model tool pages render, self-canonicalize, and are in the sitemap', async () => {
244-
const failures: string[] = []
245-
for (const [path, titlePrefix] of [
246-
['/models/pricing-calculator', 'LLM API Pricing Calculator'],
247-
['/models/token-counter', 'Token Counter'],
248-
]) {
249-
const { status, body } = await get(path)
250-
const title = decodeHtml(/<title>([^<]*)<\/title>/.exec(body)?.[1] ?? '')
251-
const canonical = /<link rel="canonical" href="([^"]+)"/.exec(body)?.[1]
252-
const robots = robotsDirectives(body)
253-
if (
254-
status !== 200 ||
255-
!title.startsWith(titlePrefix) ||
256-
title.length > 60 ||
257-
!canonical ||
258-
new URL(canonical).pathname !== path ||
259-
robots.some((directive) => directive.includes('noindex')) ||
260-
!sitemap.has(path)
261-
) {
262-
failures.push(`${path} → ${status}, title "${title}", canonical ${canonical}`)
245+
await check(
246+
'model pages carry distinct search titles within 60 characters and self-canonicalize',
247+
async () => {
248+
const comparePaths = sitemapPaths.filter((path) => path.startsWith('/models/compare/'))
249+
assert(
250+
comparePaths.length >= 5,
251+
`The sitemap lists only ${comparePaths.length} compare pages`
252+
)
253+
const paths = [
254+
'/models',
255+
'/models/anthropic',
256+
'/models/context-windows',
257+
'/models/pricing-calculator',
258+
'/models/token-counter',
259+
...comparePaths,
260+
]
261+
const pathByTitle = new Map<string, string>()
262+
let anthropicTitle = ''
263+
const failures: string[] = []
264+
for (const path of paths) {
265+
const { status, body } = await get(path)
266+
const title = pageTitle(body)
267+
const canonical = canonicalPath(body)
268+
const duplicateOf = pathByTitle.get(title)
269+
pathByTitle.set(title, duplicateOf ?? path)
270+
if (path === '/models/anthropic') anthropicTitle = title
271+
if (
272+
status !== 200 ||
273+
!title.endsWith(' | Sim') ||
274+
title.endsWith(' | Sim | Sim') ||
275+
title.length > 60 ||
276+
duplicateOf ||
277+
canonical !== path ||
278+
!sitemap.has(path)
279+
) {
280+
failures.push(
281+
`${path} → ${status}, title "${title}" (${title.length})${duplicateOf ? ` duplicates ${duplicateOf}` : ''}, canonical ${canonical}, in sitemap ${sitemap.has(path)}`
282+
)
283+
}
263284
}
285+
assert.deepEqual(failures, [])
286+
assert.match(anthropicTitle, /Claude/, '/models/anthropic does not name Claude in its title')
264287
}
265-
assert.deepEqual(failures, [])
266-
})
288+
)
267289

268-
await check('token counter ships no tiktoken rank tables in its initial load', async () => {
290+
await check('token counter ships no js-tiktoken code in its initial load', async () => {
269291
const { status, body } = await get('/models/token-counter')
270292
assert.equal(status, 200)
271-
// Every js-tiktoken rank table is an object keyed `bpe_ranks`; the tokenizer
272-
// must load only after the visitor interacts with the text box.
293+
// Every js-tiktoken rank table is keyed `bpe_ranks` and its encoder reads that property, so the
294+
// name appears in any chunk that bundles the tokenizer, which must load only on interaction.
273295
const marker = /bpe_ranks/
274-
assert(!marker.test(body), 'The initial HTML embeds a tiktoken rank table')
296+
assert(!marker.test(body), 'The initial HTML embeds js-tiktoken code')
275297
const scripts = [...body.matchAll(/<script[^>]+src="([^"]+)"/g)]
276298
.map((match) => new URL(decodeHtml(match[1]), baseUrl))
277299
.filter((url) => url.origin === baseUrl.origin)
@@ -281,7 +303,7 @@ async function run() {
281303
const script = await get(`${url.pathname}${url.search}`)
282304
if (marker.test(script.body)) offenders.push(url.pathname)
283305
}
284-
assert.deepEqual(offenders, [], 'An initial script chunk bundles tiktoken rank tables')
306+
assert.deepEqual(offenders, [], 'An initial script chunk bundles js-tiktoken')
285307
})
286308

287309
await check('docs integration slugs redirect to their landing page', async () => {
@@ -336,25 +358,6 @@ async function run() {
336358
assert.match(title, / on /, `${resellerPath} title "${title}" does not name the reseller`)
337359
})
338360

339-
await check('model pricing pages carry their search titles and canonicals', async () => {
340-
const comparePath = firstPath(/^\/models\/compare\/[^/]+-vs-[^/]+$/)
341-
const pages: Array<[path: string, title: RegExp]> = [
342-
['/models/anthropic', /^Anthropic \(Claude\) API Pricing & Models \| Sim$/],
343-
['/models/context-windows', /^LLM Context Window Comparison \| Sim$/],
344-
[comparePath, / vs .+ \| Sim$/],
345-
]
346-
const failures: string[] = []
347-
for (const [path, expected] of pages) {
348-
const { status, body } = await get(path)
349-
const title = pageTitle(body)
350-
const canonical = canonicalPath(body)
351-
if (status !== 200 || !expected.test(title) || title.length > 60 || canonical !== path) {
352-
failures.push(`${path} → ${status}, title "${title}", canonical ${canonical}`)
353-
}
354-
}
355-
assert.deepEqual(failures, [])
356-
})
357-
358361
await check('footer integration links stay on the landing site', async () => {
359362
const { body } = await get(HOME_PATH)
360363
assert(body.includes('href="/integrations/slack"'), 'Footer does not link /integrations/slack')

‎scripts/check-library-content.test.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,27 @@ describe('check-library-content', () => {
254254
expect(findings[0]).toMatchObject({ line: 3, rule: 'frontmatter' })
255255
})
256256

257+
it('rejects a published search title over 60 or description over 160 characters at the key search reads, and exempts drafts', async () => {
258+
const long = { title: 'T'.repeat(55), description: 'D'.repeat(161) }
259+
writePost('library', 'post', 'Body.', long)
260+
expect(await findingsFor('library', 'post')).toEqual([
261+
expect.objectContaining({ line: 3, rule: 'search-title' }),
262+
expect.objectContaining({ line: 4, rule: 'search-description' }),
263+
])
264+
writePost('library', 'post', 'Body.', {
265+
...long,
266+
seoTitle: 'S'.repeat(54),
267+
seoDescription: 'E'.repeat(160),
268+
})
269+
expect(await findingsFor('library', 'post')).toEqual([])
270+
writePost('library', 'post', 'Body.', { seoTitle: 'S'.repeat(55) })
271+
expect(await findingsFor('library', 'post')).toEqual([
272+
expect.objectContaining({ line: 8, rule: 'search-title' }),
273+
])
274+
writePost('library', 'post', 'Body.', { ...long, draft: 'true' })
275+
expect(await findingsFor('library', 'post')).toEqual([])
276+
})
277+
257278
it('rejects invalid YAML', async () => {
258279
writePost('library', 'post', 'Body.', { title: 'Broken: [unclosed' })
259280
expect((await findingsFor('library', 'post'))[0]).toMatchObject({ rule: 'frontmatter' })

0 commit comments

Comments
 (0)