()
const loadMarkdown = async (markdownUrl: string) => {
@@ -55,13 +61,17 @@ const IconArtwork = ({ icon }: { icon: React.ReactNode }) => (
export const DocsPageHeader = ({
description,
githubUrl,
+ implementation,
markdownUrl,
title,
+ url,
}: {
description?: string
githubUrl: string
+ implementation?: DocsImplementation
markdownUrl: string
title: string
+ url: string
}) => {
const path = useTreePath()
const icon = path.at(-1)?.icon
@@ -92,6 +102,15 @@ export const DocsPageHeader = ({
{description}
+ {implementation && (
+
+ )}
await loadMarkdown(markdownUrl)}
size="sm"
diff --git a/apps/web/src/docs/source.ts b/apps/web/src/docs/source.ts
index 5cdadbefc..b813b7eef 100644
--- a/apps/web/src/docs/source.ts
+++ b/apps/web/src/docs/source.ts
@@ -1,5 +1,7 @@
import { defineDocs } from 'fumadocs-mdx/macro'
+import { docsPageSchema } from './implementation/schema'
+
export const docs = defineDocs({
dir: 'content/docs',
docs: {
@@ -7,5 +9,6 @@ export const docs = defineDocs({
postprocess: {
includeProcessedMarkdown: true,
},
+ schema: docsPageSchema,
},
})
diff --git a/apps/web/src/docs/transport.ts b/apps/web/src/docs/transport.ts
index 3c7db9404..21b545fa7 100644
--- a/apps/web/src/docs/transport.ts
+++ b/apps/web/src/docs/transport.ts
@@ -31,6 +31,7 @@ export const getDocsPage = createServerFn({ method: 'GET' })
markdownUrl: encodeMarkdownUrl(page.slugs),
metaTitle: [page.data.title, ...ancestors].join(' - '),
path: page.path,
+ url: page.url,
}
})
diff --git a/apps/web/src/tests/implementation-prompt.test.ts b/apps/web/src/tests/implementation-prompt.test.ts
new file mode 100644
index 000000000..a2d6eccc8
--- /dev/null
+++ b/apps/web/src/tests/implementation-prompt.test.ts
@@ -0,0 +1,115 @@
+import assert from 'node:assert/strict'
+import { describe, it } from 'node:test'
+
+import type { ImplementationPromptInput } from '../docs/implementation/prompt.ts'
+
+import { buildImplementationPrompt } from '../docs/implementation/prompt.ts'
+
+const origin = 'https://docs.example.com'
+
+const input: ImplementationPromptInput = {
+ implementation: {
+ constraints: [
+ 'Use the `thread` option.',
+ 'Create the table with migrations.',
+ ],
+ decisions: [
+ 'Which plugin owns the replies?',
+ 'Which items get replies?',
+ 'How deep can replies nest?',
+ ],
+ objective: 'Add threaded replies under an existing content type.',
+ related: [
+ { href: '/docs/content-engine/public-write', title: 'Public write' },
+ {
+ href: '/docs/content-engine/reference#thread',
+ title: 'Thread reference',
+ },
+ ],
+ },
+ markdownUrl: '/docs/content-engine/threads.md',
+ origin,
+ title: 'Threaded replies',
+ url: '/docs/content-engine/threads',
+}
+
+const minimal: ImplementationPromptInput = {
+ ...input,
+ implementation: {
+ decisions: ['Which plugin owns the replies?'],
+ objective: 'Add threaded replies under an existing content type.',
+ },
+}
+
+const absoluteUrls = (prompt: string) =>
+ (prompt.match(/https:\/\/[^\s)]+/g) ?? []).map((href) => new URL(href))
+
+describe('buildImplementationPrompt', () => {
+ it('returns the same text for the same input', () => {
+ assert.equal(
+ buildImplementationPrompt(input),
+ buildImplementationPrompt(structuredClone(input)),
+ )
+ })
+
+ it('lists the decisions numbered in order and the constraints', () => {
+ const prompt = buildImplementationPrompt(input)
+
+ assert.ok(
+ prompt.includes(
+ '1. Which plugin owns the replies?\n2. Which items get replies?\n3. How deep can replies nest?',
+ ),
+ )
+ assert.ok(
+ prompt.includes(
+ 'Rules for this feature:\n\n- Use the `thread` option.\n- Create the table with migrations.',
+ ),
+ )
+ })
+
+ it('leaves out the rules block and related links when the page has none', () => {
+ const prompt = buildImplementationPrompt(minimal)
+
+ assert.ok(!prompt.includes('Rules for this feature'))
+ assert.deepEqual(
+ absoluteUrls(prompt).map((url) => url.href),
+ [
+ `${origin}/docs/content-engine/threads`,
+ `${origin}/docs/content-engine/threads.md`,
+ ],
+ )
+ })
+
+ it('points every link at the given origin and drops the anchor only from Markdown links', () => {
+ const prompt = buildImplementationPrompt(input)
+ const urls = absoluteUrls(prompt)
+
+ assert.deepEqual(
+ urls.map((url) => url.href),
+ [
+ `${origin}/docs/content-engine/threads`,
+ `${origin}/docs/content-engine/threads.md`,
+ `${origin}/docs/content-engine/public-write`,
+ `${origin}/docs/content-engine/public-write.md`,
+ `${origin}/docs/content-engine/reference#thread`,
+ `${origin}/docs/content-engine/reference.md`,
+ ],
+ )
+ for (const url of urls) {
+ assert.equal(url.origin, origin)
+ }
+ })
+
+ it('tells the agent to resolve ambiguity before editing files', () => {
+ const prompt = buildImplementationPrompt(input)
+
+ for (const phrase of [
+ 'Settle these decisions with me before you edit files',
+ 'Never pick an ambiguous target silently',
+ 'Skip a decision I already answered',
+ 'Otherwise ask in plain text with short numbered options',
+ ]) {
+ assert.ok(prompt.includes(phrase), `missing: ${phrase}`)
+ }
+ })
+})
diff --git a/apps/web/src/tests/implementation-schema.test.ts b/apps/web/src/tests/implementation-schema.test.ts
new file mode 100644
index 000000000..993875c63
--- /dev/null
+++ b/apps/web/src/tests/implementation-schema.test.ts
@@ -0,0 +1,75 @@
+import { frontmatter } from 'fumadocs-core/content/md/frontmatter'
+import assert from 'node:assert/strict'
+import { existsSync, readdirSync, readFileSync } from 'node:fs'
+import { join, resolve } from 'node:path'
+import { describe, it } from 'node:test'
+
+import { docsPageSchema } from '../docs/implementation/schema.ts'
+
+const docsDir = resolve(import.meta.dirname, '../../content/docs')
+
+const implementation = {
+ decisions: ['Which plugin owns the replies?'],
+ objective: 'Add threaded replies under an existing content type.',
+}
+
+describe('docsPageSchema', () => {
+ it('accepts a page without implementation metadata', () => {
+ assert.ok(docsPageSchema.safeParse({ title: 'Plain page' }).success)
+ })
+
+ const invalid: [string, unknown][] = [
+ ['an unknown key', { ...implementation, steps: ['Do it'] }],
+ ['an empty decisions list', { ...implementation, decisions: [] }],
+ [
+ 'a related link outside /docs',
+ {
+ ...implementation,
+ related: [{ href: 'https://example.com/docs/x', title: 'X' }],
+ },
+ ],
+ ]
+
+ for (const [name, value] of invalid) {
+ it(`rejects ${name}`, () => {
+ assert.ok(
+ !docsPageSchema.safeParse({ implementation: value, title: 'Guide' })
+ .success,
+ )
+ })
+ }
+})
+
+describe('guide implementation metadata', () => {
+ const guides = readdirSync(docsDir, { recursive: true, encoding: 'utf8' })
+ .filter((path) => path.endsWith('.mdx'))
+ .map((path) => ({
+ data: frontmatter(readFileSync(join(docsDir, path), 'utf8')).data,
+ path,
+ }))
+ .filter(
+ ({ data }) =>
+ typeof data === 'object' && data !== null && 'implementation' in data,
+ )
+
+ it('is declared on several guides', () => {
+ assert.ok(guides.length >= 3, `found ${guides.length}`)
+ })
+
+ for (const { data, path } of guides) {
+ it(`${path} is valid and links only to existing docs pages`, () => {
+ const page = docsPageSchema.parse(data)
+
+ assert.ok(page.implementation, 'implementation frontmatter is missing')
+ for (const { href } of page.implementation.related ?? []) {
+ const slug = href.replace(/^\/docs\//, '').replace(/#.*$/, '')
+
+ assert.ok(
+ existsSync(join(docsDir, `${slug}.mdx`)) ||
+ existsSync(join(docsDir, slug, 'index.mdx')),
+ `${href} has no matching .mdx page`,
+ )
+ }
+ })
+ }
+})
diff --git a/packages/vitnode/src/framework/vite/ssr-externals.ts b/packages/vitnode/src/framework/vite/ssr-externals.ts
index 0a9f5f794..698a3dac9 100644
--- a/packages/vitnode/src/framework/vite/ssr-externals.ts
+++ b/packages/vitnode/src/framework/vite/ssr-externals.ts
@@ -26,14 +26,7 @@ export const vitNodeSsrExternals = ({
config: async (_userConfig, { command }) => ({
nitro:
command === "build"
- ? {
- // Writes `.gz`/`.br` next to every static asset and prerendered
- // page at build time; Nitro's static handler serves the one the
- // browser accepts, so no CPU is spent compressing per request.
- // An app's own `nitro({ compressPublicAssets })` still wins.
- compressPublicAssets: { brotli: true, gzip: true },
- traceDeps: [...NITRO_TRACED_DEPENDENCIES],
- }
+ ? { traceDeps: [...NITRO_TRACED_DEPENDENCIES] }
: undefined,
ssr: {
external: