diff --git a/.agents/index.md b/.agents/index.md index ea6eee1cb..a73a528f2 100644 --- a/.agents/index.md +++ b/.agents/index.md @@ -44,3 +44,22 @@ Inspired by https://github.com/DietrichGebert/ponytail - [UI Style Guide](./ui-style.md): Visual design principles for 2026 - [Workflow](./workflow.md): Build commands, debugging, Playwright - [Analytics](./analytics.md): GA4 event taxonomy, funnel definition, custom dimensions + +## Writing and documentation + +Use `tanner-writing-style` for public front-page headings and marketing copy, +and `editorial-restraint` for clarity and grounded claims, when those skills +are available. Front-page copy should be personable, punchy, and informal. +Lead with the benefit and preserve supplied sales emphasis while polishing +awkward phrasing. Concrete grounding does not require API names in every +headline or subhead; examples and linked documentation can carry that detail. + +Keep API references and technical guides precise and approachable. Preserve +identifiers and explain actual behavior and relevant limitations without +imposing the marketing voice on reference documentation. + +Read relevant implementation, contracts, and examples before changing product +claims. Keep capabilities, measurements, guarantees, and comparisons accurate. +If a consequential claim is unsupported, offer a stronger accurate alternative +briefly and continue within the requested scope. Subjective praise and useful +promotional hooks do not need to become dry technical prose. diff --git a/src/components/landing/HighlightLanding.tsx b/src/components/landing/HighlightLanding.tsx index a2359a166..e2db2f5ef 100644 --- a/src/components/landing/HighlightLanding.tsx +++ b/src/components/landing/HighlightLanding.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { Link } from '@tanstack/react-router' -import { ArrowRightIcon, CheckIcon } from '@phosphor-icons/react' +import { ArrowRightIcon } from '@phosphor-icons/react' import { LibraryWordmark } from '~/components/LibraryWordmark' import { getLibrary } from '~/libraries' @@ -24,24 +24,24 @@ const bundleProfiles = [ name: 'core', detail: 'no languages', size: '1.82 KB', - width: 'w-[22%]', + width: 'w-[17%]', }, { name: 'tsx', detail: 'core + TSX', - size: '4.03 KB', - width: 'w-[49%]', + size: '4.29 KB', + width: 'w-[40%]', }, { name: 'docs', detail: '9 languages', - size: '5.97 KB', - width: 'w-[72%]', + size: '6.22 KB', + width: 'w-[58%]', }, { name: 'all', - detail: '26 languages', - size: '8.29 KB', + detail: '30 languages', + size: '10.79 KB', width: 'w-full', }, ] @@ -60,7 +60,7 @@ export default function HighlightLanding() {
@@ -83,15 +83,15 @@ export default function HighlightLanding() {
@@ -115,44 +115,17 @@ export default function HighlightLanding() {
-
- - -
-
- -
- - - -
-
-
+

- A serializable document model that keeps highlighting at an - explicit boundary. + Parse and render Markdown, then add syntax highlighting with a + callback.

Explore Markdown @@ -359,7 +332,7 @@ function BundleDial() { return (
- 0.0.11 · gzip · KB = 1,000 bytes + 1.0.0 · gzip · KB = 1,000 bytes
{bundleProfiles.map((profile) => ( @@ -379,6 +352,16 @@ function BundleDial() {
))}
+

+ Minified browser source-entry builds, measured October 1, 2026 with Node + 26.3.1. Sizes vary with imports and build tools.{' '} + + Measurement details + +

) } @@ -550,90 +533,3 @@ function AnnotatedLine({
) } - -function BenchmarkTape() { - return ( -
-
- 334 docs fixtures - warmed time - HTML -
- - -

- Local measurements from the{' '} - - project benchmark report - - . Shiki initialization and language loading are measured separately; - EJS, ENV, and TSRX use its plaintext fallback. Timings vary by machine, - and the tools don’t provide equivalent grammar accuracy. -

-
- ) -} - -function BenchmarkRow({ - emphasis, - name, - output, - time, -}: { - emphasis: boolean - name: string - output: string - time: string -}) { - return ( -
- - {name} - - {time} - {output} -
- ) -} - -function ChoiceRow({ name, useWhen }: { name: string; useWhen: string }) { - return ( -
-
-
-

{useWhen}

-
- ) -} diff --git a/src/components/landing/LibraryLanding.tsx b/src/components/landing/LibraryLanding.tsx index 20cbc89d2..b2d8f52fb 100644 --- a/src/components/landing/LibraryLanding.tsx +++ b/src/components/landing/LibraryLanding.tsx @@ -393,7 +393,7 @@ export function LandingSectionIntro({ centered?: boolean eyebrow?: string icon?: React.ReactNode - title: string + title: React.ReactNode }) { return (
diff --git a/src/components/landing/MarkdownLanding.tsx b/src/components/landing/MarkdownLanding.tsx index d9c777102..35b7766c4 100644 --- a/src/components/landing/MarkdownLanding.tsx +++ b/src/components/landing/MarkdownLanding.tsx @@ -27,7 +27,7 @@ const highlightLibrary = getLibrary('highlight') const markdownPrompt = [ 'Build a technical content renderer with TanStack Markdown.', - 'Treat its serializable AST as the durable document model, render from that tree with HTML, React, or Octane, and enable only the syntax extensions the product needs.', + 'Parse Markdown into a serializable document tree, render it with HTML, React, or Octane, and enable only the syntax extensions the product needs.', 'For accumulated AI responses, use the optional streaming profile without carrying incremental parser state between updates.', 'Preserve the safe defaults and deterministic output, and keep syntax highlighting as an explicit external integration.', ].join(' ') @@ -117,7 +117,7 @@ const streamingSource = `# Streaming response The model can send **ordinary Markdown** as it thinks. -- completed blocks stay stable +- accumulated text is reparsed on each update - unfinished markers stay out of the way - React and HTML stay in sync @@ -133,20 +133,20 @@ const streamingExtensions = [streamingMarkdownExtension()] const bundleComparisons = [ { name: 'TanStack HTML', - size: '6.7 KB', - width: 'w-[13%]', + size: '7.13 KB', + width: 'w-[14%]', emphasis: true, }, - { name: 'marked', size: '12.5 KB', width: 'w-[24%]', emphasis: false }, + { name: 'marked', size: '12.47 KB', width: 'w-[24%]', emphasis: false }, { name: 'unified stack', - size: '36.8 KB', + size: '36.62 KB', width: 'w-[70%]', emphasis: false, }, { name: 'markdown-it', - size: '52.7 KB', + size: '52.06 KB', width: 'w-full', emphasis: false, }, @@ -156,8 +156,8 @@ export default function MarkdownLanding() { return ( } prompt={markdownPrompt} promptLabel="Copy Markdown prompt" @@ -165,8 +165,8 @@ export default function MarkdownLanding() { @@ -174,8 +174,8 @@ export default function MarkdownLanding() {
@@ -184,8 +184,8 @@ export default function MarkdownLanding() {
+ + User-generated Markdown? 😍 + + + Malicious XSS? ❌ + + + } + body="Render user content with fewer worries. We escape raw HTML and block executable URLs by default, and you stay in control of custom rendering." />
@@ -212,8 +221,8 @@ export default function MarkdownLanding() {
@@ -222,23 +231,34 @@ export default function MarkdownLanding() {
+ Got a favorite syntax highlighter? Bring it along, or use our + own{' '} + + TanStack Highlight + + . We handle the Markdown and code-block markup, you handle the + colors. + + } /> -

- Companion, not dependency -

-
+

- Synchronous highlighting for the code fences the document model - already understands. + A tiny, synchronous highlighter that plugs right into Markdown’s + code fences.

Explore Highlight @@ -696,10 +716,10 @@ function BundleLedger() { ))}
{[ - ['4.9 KB', 'Parser'], - ['6.6 KB', 'React adapter'], - ['6.6 KB', 'Octane adapter'], - ['2.3 KB', 'docs preset'], + ['5.29 KB', 'Parser'], + ['7.09 KB', 'React adapter'], + ['7.06 KB', 'Octane adapter'], + ['2.37 KB', 'docs preset'], ].map(([value, label]) => (
{value}
@@ -710,10 +730,12 @@ function BundleLedger() { ))}

- Gzip sizes for minified browser bundles. Renderers include the parser; - framework runtimes and highlighters are excluded. Feature sets differ.{' '} + Markdown 1.0.0 · measured October 1, 2026 with Node 26.3.1. Gzip sizes + for minified browser builds of public entry exports. Renderers include + the parser; framework runtimes and highlighters are excluded. Feature + sets differ, and sizes vary with imports and build tools.{' '} Measurement details diff --git a/src/libraries/libraries.ts b/src/libraries/libraries.ts index 06a5682b9..51b10b7c7 100644 --- a/src/libraries/libraries.ts +++ b/src/libraries/libraries.ts @@ -625,9 +625,9 @@ export const markdown: LibrarySlim = { frameworks: ['react', 'vanilla'], corePackageName: '@tanstack/markdown', npmPackageNames: ['@tanstack/markdown'], - latestVersion: 'v0', + latestVersion: 'v1', latestBranch: 'main', - availableVersions: ['v0'], + availableVersions: ['v1'], defaultDocs: 'overview', sitemap: { includeLandingPage: true, @@ -656,9 +656,9 @@ export const highlight: LibrarySlim = { frameworks: ['react', 'vanilla'], corePackageName: '@tanstack/highlight', npmPackageNames: ['@tanstack/highlight'], - latestVersion: 'v0', + latestVersion: 'v1', latestBranch: 'main', - availableVersions: ['v0'], + availableVersions: ['v1'], defaultDocs: 'overview', sitemap: { includeLandingPage: true, diff --git a/tests/markdown-highlight-versions.test.ts b/tests/markdown-highlight-versions.test.ts new file mode 100644 index 000000000..182f9a337 --- /dev/null +++ b/tests/markdown-highlight-versions.test.ts @@ -0,0 +1,62 @@ +import assert from 'node:assert/strict' +import test from 'node:test' +import { getBranch, highlight, markdown } from '../src/libraries' +import { validateLibraryVersion } from '../src/routes/-library-landing' + +for (const library of [markdown, highlight]) { + test(`${library.id} v1 picker uses the current docs branch and retires v0`, () => { + assert.equal(library.latestVersion, 'v1') + assert.deepEqual(library.availableVersions, ['v1']) + assert.equal(getBranch(library, 'v1'), 'main') + assert.equal(getBranch(library, 'latest'), 'main') + + const invalidVersion = () => { + throw new Error('retired version') + } + for (const version of ['latest', 'v1']) { + assert.equal( + validateLibraryVersion(library.id, version, invalidVersion), + library, + ) + } + assert.throws( + () => validateLibraryVersion(library.id, 'v0', invalidVersion), + /retired version/, + ) + }) +} + +const baseUrl = process.env.TANSTACK_DOCS_SMOKE_BASE_URL + +test( + 'Markdown and Highlight v0 and v1 docs aliases preserve paths and query strings', + { + skip: baseUrl ? false : 'Set TANSTACK_DOCS_SMOKE_BASE_URL for route checks', + }, + async () => { + assert.ok(baseUrl) + for (const library of [markdown, highlight]) { + for (const version of ['v0', 'v1']) { + const response: Response = await fetch( + new URL( + `/${library.id}/${version}/docs/overview?test=version`, + baseUrl, + ), + { redirect: 'manual', signal: AbortSignal.timeout(30_000) }, + ) + assert.equal(response.status, 308) + const location = response.headers.get('location') + assert.ok(location) + const target = new URL(location, baseUrl) + assert.equal(target.pathname, `/${library.id}/latest/docs/overview`) + assert.equal(target.search, '?test=version') + } + const response: Response = await fetch( + new URL(`/${library.id}/latest/docs/overview`, baseUrl), + { signal: AbortSignal.timeout(30_000) }, + ) + assert.equal(response.status, 200) + assert.match(await response.text(), /v1/) + } + }, +)