Releases: photon-hq/vellum
Release list
@vellum-docs/profile-mintlify@0.3.0
Patch Changes
-
ad1aa14: Add
cellfilter +TypeString.onelinefor cell-safe renderingEvery adopter was hand-rolling the same 5-filter escape chain when dropping types or summaries into markdown table cells:
{{ m.type.text | replace("\n"," ") | replace(" ","") | replace("|","\\|") | replace("<","<") | replace(">",">") }}
Two additions collapse that:
TypeString.oneline?: string- populated at extraction time with the whitespace-collapsed form oftext. Omitted when equal totext(single-line case). Fixes the\n+ indentation problem at source, before any template filter runs.cellfilter (profile-routed) - accepts aTypeString, plain string, or null. Collapses whitespace as defence-in-depth, routes through the profile's newcell(value, ctx)method, which wraps in a code span and escapes|. Works for anything cell-bound, not just types.Before:
| `{{ m.name }}` | `{{ m.type.text | replace("\n"," ") | replace(" ","") | replace("|","\\|") | replace("<","<") | replace(">",">") }}` | {{ m.doc.summary }} |
After:
| `{{ m.name }}` | {{ m.type | cell | safe }} | {{ m.doc.summary | cell | safe }} |
Schema additions (additive)
TypeString.oneline?: stringRendererProfile.cell(value: string, ctx: RenderContext): string
Existing extractors keep working - when
onelineis absent the filter falls back to.text. Existing profiles get the new method implemented inMarkdownProfileandMintlifyProfile; third-party profiles must add acellimplementation.Out of scope
jsx-propandfencedcontexts from the original request. Neither has recurring template pain today; defer until they do. -
6503a35: Philosophy audit fixes - strict-by-default, dead schema cleanup
An audit against the newly-written PHILOSOPHY.md surfaced four gaps. This changeset closes them.
Strict template rendering is now on by default. Principle 11 ("fail loudly at build time") was being violated by
throwOnUndefined: false- a template with a typo ({{ fn.doc.summaryy }}instead offn.doc.summary) silently rendered as empty string, and the docs shipped with a blank section. TheNunjucksEnginenow defaults to strict rendering: any output of an undefined value throws, which bubbles to a non-zero build exit.Opt-out paths, for the rare cases where silent fallback is wanted during migration:
- Config:
new NunjucksEngine({ strict: false }). - CLI:
vellum build --no-strict.
This is a behavior change. Templates that relied on silent-empty for undefined values will now fail. Typical patterns that are still safe:
{% if sym.members %},{{ sym.doc.summary }}(empty string is defined),{% for m in sym.members or [] %}. The patterns that will now break are the ones you wanted to know about anyway.Schema cleanup. Three dead schema fields removed - they were defined but never populated by any extractor, violating principle 7 ("80% case defines the schema"):
Symbol.signatureResolved?: string- removed.Member.kindvalues'index'and'call'- removed from the union. Can be added back with implementation when a TS call/index-signature extractor lands or a language that needs them ships.
Docs drift fixes. ARCHITECTURE.md referenced a
{{ str | tsdoc }}filter that never existed; replaced with{{ sym | summary }}(which does). Principle 2 in PHILOSOPHY.md now explicitly distinguishes "pattern-aware" (OK) from "language-idiosyncratic" (not OK), soSymbol.discriminator?is consistent with the stated rule. - Config:
-
Updated dependencies [2a9986c]
-
Updated dependencies [ad1aa14]
-
Updated dependencies [6503a35]
- @vellum-docs/core@0.3.0
@vellum-docs/profile-markdown@0.3.0
Patch Changes
-
ad1aa14: Add
cellfilter +TypeString.onelinefor cell-safe renderingEvery adopter was hand-rolling the same 5-filter escape chain when dropping types or summaries into markdown table cells:
{{ m.type.text | replace("\n"," ") | replace(" ","") | replace("|","\\|") | replace("<","<") | replace(">",">") }}
Two additions collapse that:
TypeString.oneline?: string- populated at extraction time with the whitespace-collapsed form oftext. Omitted when equal totext(single-line case). Fixes the\n+ indentation problem at source, before any template filter runs.cellfilter (profile-routed) - accepts aTypeString, plain string, or null. Collapses whitespace as defence-in-depth, routes through the profile's newcell(value, ctx)method, which wraps in a code span and escapes|. Works for anything cell-bound, not just types.Before:
| `{{ m.name }}` | `{{ m.type.text | replace("\n"," ") | replace(" ","") | replace("|","\\|") | replace("<","<") | replace(">",">") }}` | {{ m.doc.summary }} |
After:
| `{{ m.name }}` | {{ m.type | cell | safe }} | {{ m.doc.summary | cell | safe }} |
Schema additions (additive)
TypeString.oneline?: stringRendererProfile.cell(value: string, ctx: RenderContext): string
Existing extractors keep working - when
onelineis absent the filter falls back to.text. Existing profiles get the new method implemented inMarkdownProfileandMintlifyProfile; third-party profiles must add acellimplementation.Out of scope
jsx-propandfencedcontexts from the original request. Neither has recurring template pain today; defer until they do. -
6503a35: Philosophy audit fixes - strict-by-default, dead schema cleanup
An audit against the newly-written PHILOSOPHY.md surfaced four gaps. This changeset closes them.
Strict template rendering is now on by default. Principle 11 ("fail loudly at build time") was being violated by
throwOnUndefined: false- a template with a typo ({{ fn.doc.summaryy }}instead offn.doc.summary) silently rendered as empty string, and the docs shipped with a blank section. TheNunjucksEnginenow defaults to strict rendering: any output of an undefined value throws, which bubbles to a non-zero build exit.Opt-out paths, for the rare cases where silent fallback is wanted during migration:
- Config:
new NunjucksEngine({ strict: false }). - CLI:
vellum build --no-strict.
This is a behavior change. Templates that relied on silent-empty for undefined values will now fail. Typical patterns that are still safe:
{% if sym.members %},{{ sym.doc.summary }}(empty string is defined),{% for m in sym.members or [] %}. The patterns that will now break are the ones you wanted to know about anyway.Schema cleanup. Three dead schema fields removed - they were defined but never populated by any extractor, violating principle 7 ("80% case defines the schema"):
Symbol.signatureResolved?: string- removed.Member.kindvalues'index'and'call'- removed from the union. Can be added back with implementation when a TS call/index-signature extractor lands or a language that needs them ships.
Docs drift fixes. ARCHITECTURE.md referenced a
{{ str | tsdoc }}filter that never existed; replaced with{{ sym | summary }}(which does). Principle 2 in PHILOSOPHY.md now explicitly distinguishes "pattern-aware" (OK) from "language-idiosyncratic" (not OK), soSymbol.discriminator?is consistent with the stated rule. - Config:
-
Updated dependencies [2a9986c]
-
Updated dependencies [ad1aa14]
-
Updated dependencies [6503a35]
- @vellum-docs/core@0.3.0
@vellum-docs/language-server@0.3.0
Patch Changes
-
ad1aa14: Add
cellfilter +TypeString.onelinefor cell-safe renderingEvery adopter was hand-rolling the same 5-filter escape chain when dropping types or summaries into markdown table cells:
{{ m.type.text | replace("\n"," ") | replace(" ","") | replace("|","\\|") | replace("<","<") | replace(">",">") }}
Two additions collapse that:
TypeString.oneline?: string- populated at extraction time with the whitespace-collapsed form oftext. Omitted when equal totext(single-line case). Fixes the\n+ indentation problem at source, before any template filter runs.cellfilter (profile-routed) - accepts aTypeString, plain string, or null. Collapses whitespace as defence-in-depth, routes through the profile's newcell(value, ctx)method, which wraps in a code span and escapes|. Works for anything cell-bound, not just types.Before:
| `{{ m.name }}` | `{{ m.type.text | replace("\n"," ") | replace(" ","") | replace("|","\\|") | replace("<","<") | replace(">",">") }}` | {{ m.doc.summary }} |
After:
| `{{ m.name }}` | {{ m.type | cell | safe }} | {{ m.doc.summary | cell | safe }} |
Schema additions (additive)
TypeString.oneline?: stringRendererProfile.cell(value: string, ctx: RenderContext): string
Existing extractors keep working - when
onelineis absent the filter falls back to.text. Existing profiles get the new method implemented inMarkdownProfileandMintlifyProfile; third-party profiles must add acellimplementation.Out of scope
jsx-propandfencedcontexts from the original request. Neither has recurring template pain today; defer until they do. -
6503a35: Philosophy audit fixes - strict-by-default, dead schema cleanup
An audit against the newly-written PHILOSOPHY.md surfaced four gaps. This changeset closes them.
Strict template rendering is now on by default. Principle 11 ("fail loudly at build time") was being violated by
throwOnUndefined: false- a template with a typo ({{ fn.doc.summaryy }}instead offn.doc.summary) silently rendered as empty string, and the docs shipped with a blank section. TheNunjucksEnginenow defaults to strict rendering: any output of an undefined value throws, which bubbles to a non-zero build exit.Opt-out paths, for the rare cases where silent fallback is wanted during migration:
- Config:
new NunjucksEngine({ strict: false }). - CLI:
vellum build --no-strict.
This is a behavior change. Templates that relied on silent-empty for undefined values will now fail. Typical patterns that are still safe:
{% if sym.members %},{{ sym.doc.summary }}(empty string is defined),{% for m in sym.members or [] %}. The patterns that will now break are the ones you wanted to know about anyway.Schema cleanup. Three dead schema fields removed - they were defined but never populated by any extractor, violating principle 7 ("80% case defines the schema"):
Symbol.signatureResolved?: string- removed.Member.kindvalues'index'and'call'- removed from the union. Can be added back with implementation when a TS call/index-signature extractor lands or a language that needs them ships.
Docs drift fixes. ARCHITECTURE.md referenced a
{{ str | tsdoc }}filter that never existed; replaced with{{ sym | summary }}(which does). Principle 2 in PHILOSOPHY.md now explicitly distinguishes "pattern-aware" (OK) from "language-idiosyncratic" (not OK), soSymbol.discriminator?is consistent with the stated rule. - Config:
-
Updated dependencies [2a9986c]
-
Updated dependencies [ad1aa14]
-
Updated dependencies [6503a35]
- @vellum-docs/core@0.3.0
@vellum-docs/extractor-typescript@0.3.0
Patch Changes
-
ad1aa14: Add
cellfilter +TypeString.onelinefor cell-safe renderingEvery adopter was hand-rolling the same 5-filter escape chain when dropping types or summaries into markdown table cells:
{{ m.type.text | replace("\n"," ") | replace(" ","") | replace("|","\\|") | replace("<","<") | replace(">",">") }}
Two additions collapse that:
TypeString.oneline?: string- populated at extraction time with the whitespace-collapsed form oftext. Omitted when equal totext(single-line case). Fixes the\n+ indentation problem at source, before any template filter runs.cellfilter (profile-routed) - accepts aTypeString, plain string, or null. Collapses whitespace as defence-in-depth, routes through the profile's newcell(value, ctx)method, which wraps in a code span and escapes|. Works for anything cell-bound, not just types.Before:
| `{{ m.name }}` | `{{ m.type.text | replace("\n"," ") | replace(" ","") | replace("|","\\|") | replace("<","<") | replace(">",">") }}` | {{ m.doc.summary }} |
After:
| `{{ m.name }}` | {{ m.type | cell | safe }} | {{ m.doc.summary | cell | safe }} |
Schema additions (additive)
TypeString.oneline?: stringRendererProfile.cell(value: string, ctx: RenderContext): string
Existing extractors keep working - when
onelineis absent the filter falls back to.text. Existing profiles get the new method implemented inMarkdownProfileandMintlifyProfile; third-party profiles must add acellimplementation.Out of scope
jsx-propandfencedcontexts from the original request. Neither has recurring template pain today; defer until they do. -
6503a35: Philosophy audit fixes - strict-by-default, dead schema cleanup
An audit against the newly-written PHILOSOPHY.md surfaced four gaps. This changeset closes them.
Strict template rendering is now on by default. Principle 11 ("fail loudly at build time") was being violated by
throwOnUndefined: false- a template with a typo ({{ fn.doc.summaryy }}instead offn.doc.summary) silently rendered as empty string, and the docs shipped with a blank section. TheNunjucksEnginenow defaults to strict rendering: any output of an undefined value throws, which bubbles to a non-zero build exit.Opt-out paths, for the rare cases where silent fallback is wanted during migration:
- Config:
new NunjucksEngine({ strict: false }). - CLI:
vellum build --no-strict.
This is a behavior change. Templates that relied on silent-empty for undefined values will now fail. Typical patterns that are still safe:
{% if sym.members %},{{ sym.doc.summary }}(empty string is defined),{% for m in sym.members or [] %}. The patterns that will now break are the ones you wanted to know about anyway.Schema cleanup. Three dead schema fields removed - they were defined but never populated by any extractor, violating principle 7 ("80% case defines the schema"):
Symbol.signatureResolved?: string- removed.Member.kindvalues'index'and'call'- removed from the union. Can be added back with implementation when a TS call/index-signature extractor lands or a language that needs them ships.
Docs drift fixes. ARCHITECTURE.md referenced a
{{ str | tsdoc }}filter that never existed; replaced with{{ sym | summary }}(which does). Principle 2 in PHILOSOPHY.md now explicitly distinguishes "pattern-aware" (OK) from "language-idiosyncratic" (not OK), soSymbol.discriminator?is consistent with the stated rule. - Config:
-
Updated dependencies [2a9986c]
-
Updated dependencies [ad1aa14]
-
Updated dependencies [6503a35]
- @vellum-docs/core@0.3.0
@vellum-docs/engine-nunjucks@0.3.0
Minor Changes
-
2a9986c: Add
vellum build --watchwith per-template invalidationEdit a template or a source file and Vellum re-renders only the output files affected by the change. A docs project that wires
vellum build --watchalongside the host's dev server (Next.js, Mintlify, etc.) no longer needs a restart loop to see TSDoc edits.vellum build --watch
How invalidation works
Templates are instrumented while rendering. The
symbol(),symbols(), andmodule()globals record what each.velfile actually read:symbol(id)- the looked-upSymbolId,module(path)- the module path,symbols(query)- the verbatim query and the resulting ids.
On a source-file change, Vellum diffs the file's old vs new symbols (source-position-insensitive content hash) and re-renders templates whose recorded reads intersect the diff. Added symbols are re-matched against each template's prior queries so a new
symbols({ tag: 'foo' })result triggers the right renders.Template edits re-render only that template. Config changes tear down the watcher and re-prime. Render and extract errors log and keep the previous output on disk - the watcher stays alive.
API changes (minor - pre-1.0)
TemplateEngine.renderreturnsRenderResult, notstring. Custom engines update the return shape:// before async render(source, ctx): Promise<string> { ... } // after async render(source, ctx): Promise<RenderResult> { return { output } // or { output, reads } when ctx.reads was supplied }
SymbolIndexgainssymbolsByFileandremoveByFile. Custom index implementations need both.InMemorySymbolIndexnow tracks per-file provenance viaSymbol.source.file.matchesQuery(sym, query)is exported from@vellum-docs/core- the exact predicateInMemorySymbolIndex.symbols()uses, reusable by tooling that needs to test a single symbol against a query.New exports (
@vellum-docs/core)TemplateReads,createTemplateReads()SymbolDiff,diffSymbols,hashSymbol,mergeDiffs,emptyDiff,isEmptyDiffDependencyGraphVellum.extractLanguage,Vellum.renderTemplate,Vellum.listTemplates
New exports (
@vellum-docs/cli)runWatch,WatchCommandOptions
Watcher details
File watching uses chokidar v4 with Nuxt-style granular filtering:
node_modules,.git,.turbo,dist,build,coverage, and the resolvedoutDirare ignored anywhere in the tree, even if a user'ssources[lang].includewould otherwise cover them. Events are debounced 100ms;awaitWriteFinishhandles editor save races.Known tradeoff
Per-file TS extraction isn't meaningful in isolation (cross-file type graph), so
extractLanguagere-runs the extractor over the whole program on each batch. Invalidation still narrows the render set - which is what dominates latency for a typical docs project - but we don't yet skip extraction for unchanged files beyond the existing content-hash cache.
Patch Changes
-
ad1aa14: Add
cellfilter +TypeString.onelinefor cell-safe renderingEvery adopter was hand-rolling the same 5-filter escape chain when dropping types or summaries into markdown table cells:
{{ m.type.text | replace("\n"," ") | replace(" ","") | replace("|","\\|") | replace("<","<") | replace(">",">") }}
Two additions collapse that:
TypeString.oneline?: string- populated at extraction time with the whitespace-collapsed form oftext. Omitted when equal totext(single-line case). Fixes the\n+ indentation problem at source, before any template filter runs.cellfilter (profile-routed) - accepts aTypeString, plain string, or null. Collapses whitespace as defence-in-depth, routes through the profile's newcell(value, ctx)method, which wraps in a code span and escapes|. Works for anything cell-bound, not just types.Before:
| `{{ m.name }}` | `{{ m.type.text | replace("\n"," ") | replace(" ","") | replace("|","\\|") | replace("<","<") | replace(">",">") }}` | {{ m.doc.summary }} |
After:
| `{{ m.name }}` | {{ m.type | cell | safe }} | {{ m.doc.summary | cell | safe }} |
Schema additions (additive)
TypeString.oneline?: stringRendererProfile.cell(value: string, ctx: RenderContext): string
Existing extractors keep working - when
onelineis absent the filter falls back to.text. Existing profiles get the new method implemented inMarkdownProfileandMintlifyProfile; third-party profiles must add acellimplementation.Out of scope
jsx-propandfencedcontexts from the original request. Neither has recurring template pain today; defer until they do. -
6503a35: Philosophy audit fixes - strict-by-default, dead schema cleanup
An audit against the newly-written PHILOSOPHY.md surfaced four gaps. This changeset closes them.
Strict template rendering is now on by default. Principle 11 ("fail loudly at build time") was being violated by
throwOnUndefined: false- a template with a typo ({{ fn.doc.summaryy }}instead offn.doc.summary) silently rendered as empty string, and the docs shipped with a blank section. TheNunjucksEnginenow defaults to strict rendering: any output of an undefined value throws, which bubbles to a non-zero build exit.Opt-out paths, for the rare cases where silent fallback is wanted during migration:
- Config:
new NunjucksEngine({ strict: false }). - CLI:
vellum build --no-strict.
This is a behavior change. Templates that relied on silent-empty for undefined values will now fail. Typical patterns that are still safe:
{% if sym.members %},{{ sym.doc.summary }}(empty string is defined),{% for m in sym.members or [] %}. The patterns that will now break are the ones you wanted to know about anyway.Schema cleanup. Three dead schema fields removed - they were defined but never populated by any extractor, violating principle 7 ("80% case defines the schema"):
Symbol.signatureResolved?: string- removed.Member.kindvalues'index'and'call'- removed from the union. Can be added back with implementation when a TS call/index-signature extractor lands or a language that needs them ships.
Docs drift fixes. ARCHITECTURE.md referenced a
{{ str | tsdoc }}filter that never existed; replaced with{{ sym | summary }}(which does). Principle 2 in PHILOSOPHY.md now explicitly distinguishes "pattern-aware" (OK) from "language-idiosyncratic" (not OK), soSymbol.discriminator?is consistent with the stated rule. - Config:
-
Updated dependencies [2a9986c]
-
Updated dependencies [ad1aa14]
-
Updated dependencies [6503a35]
- @vellum-docs/core@0.3.0
@vellum-docs/core@0.3.0
Minor Changes
-
2a9986c: Add
vellum build --watchwith per-template invalidationEdit a template or a source file and Vellum re-renders only the output files affected by the change. A docs project that wires
vellum build --watchalongside the host's dev server (Next.js, Mintlify, etc.) no longer needs a restart loop to see TSDoc edits.vellum build --watch
How invalidation works
Templates are instrumented while rendering. The
symbol(),symbols(), andmodule()globals record what each.velfile actually read:symbol(id)- the looked-upSymbolId,module(path)- the module path,symbols(query)- the verbatim query and the resulting ids.
On a source-file change, Vellum diffs the file's old vs new symbols (source-position-insensitive content hash) and re-renders templates whose recorded reads intersect the diff. Added symbols are re-matched against each template's prior queries so a new
symbols({ tag: 'foo' })result triggers the right renders.Template edits re-render only that template. Config changes tear down the watcher and re-prime. Render and extract errors log and keep the previous output on disk - the watcher stays alive.
API changes (minor - pre-1.0)
TemplateEngine.renderreturnsRenderResult, notstring. Custom engines update the return shape:// before async render(source, ctx): Promise<string> { ... } // after async render(source, ctx): Promise<RenderResult> { return { output } // or { output, reads } when ctx.reads was supplied }
SymbolIndexgainssymbolsByFileandremoveByFile. Custom index implementations need both.InMemorySymbolIndexnow tracks per-file provenance viaSymbol.source.file.matchesQuery(sym, query)is exported from@vellum-docs/core- the exact predicateInMemorySymbolIndex.symbols()uses, reusable by tooling that needs to test a single symbol against a query.New exports (
@vellum-docs/core)TemplateReads,createTemplateReads()SymbolDiff,diffSymbols,hashSymbol,mergeDiffs,emptyDiff,isEmptyDiffDependencyGraphVellum.extractLanguage,Vellum.renderTemplate,Vellum.listTemplates
New exports (
@vellum-docs/cli)runWatch,WatchCommandOptions
Watcher details
File watching uses chokidar v4 with Nuxt-style granular filtering:
node_modules,.git,.turbo,dist,build,coverage, and the resolvedoutDirare ignored anywhere in the tree, even if a user'ssources[lang].includewould otherwise cover them. Events are debounced 100ms;awaitWriteFinishhandles editor save races.Known tradeoff
Per-file TS extraction isn't meaningful in isolation (cross-file type graph), so
extractLanguagere-runs the extractor over the whole program on each batch. Invalidation still narrows the render set - which is what dominates latency for a typical docs project - but we don't yet skip extraction for unchanged files beyond the existing content-hash cache.
Patch Changes
-
ad1aa14: Add
cellfilter +TypeString.onelinefor cell-safe renderingEvery adopter was hand-rolling the same 5-filter escape chain when dropping types or summaries into markdown table cells:
{{ m.type.text | replace("\n"," ") | replace(" ","") | replace("|","\\|") | replace("<","<") | replace(">",">") }}
Two additions collapse that:
TypeString.oneline?: string- populated at extraction time with the whitespace-collapsed form oftext. Omitted when equal totext(single-line case). Fixes the\n+ indentation problem at source, before any template filter runs.cellfilter (profile-routed) - accepts aTypeString, plain string, or null. Collapses whitespace as defence-in-depth, routes through the profile's newcell(value, ctx)method, which wraps in a code span and escapes|. Works for anything cell-bound, not just types.Before:
| `{{ m.name }}` | `{{ m.type.text | replace("\n"," ") | replace(" ","") | replace("|","\\|") | replace("<","<") | replace(">",">") }}` | {{ m.doc.summary }} |
After:
| `{{ m.name }}` | {{ m.type | cell | safe }} | {{ m.doc.summary | cell | safe }} |
Schema additions (additive)
TypeString.oneline?: stringRendererProfile.cell(value: string, ctx: RenderContext): string
Existing extractors keep working - when
onelineis absent the filter falls back to.text. Existing profiles get the new method implemented inMarkdownProfileandMintlifyProfile; third-party profiles must add acellimplementation.Out of scope
jsx-propandfencedcontexts from the original request. Neither has recurring template pain today; defer until they do. -
6503a35: Philosophy audit fixes - strict-by-default, dead schema cleanup
An audit against the newly-written PHILOSOPHY.md surfaced four gaps. This changeset closes them.
Strict template rendering is now on by default. Principle 11 ("fail loudly at build time") was being violated by
throwOnUndefined: false- a template with a typo ({{ fn.doc.summaryy }}instead offn.doc.summary) silently rendered as empty string, and the docs shipped with a blank section. TheNunjucksEnginenow defaults to strict rendering: any output of an undefined value throws, which bubbles to a non-zero build exit.Opt-out paths, for the rare cases where silent fallback is wanted during migration:
- Config:
new NunjucksEngine({ strict: false }). - CLI:
vellum build --no-strict.
This is a behavior change. Templates that relied on silent-empty for undefined values will now fail. Typical patterns that are still safe:
{% if sym.members %},{{ sym.doc.summary }}(empty string is defined),{% for m in sym.members or [] %}. The patterns that will now break are the ones you wanted to know about anyway.Schema cleanup. Three dead schema fields removed - they were defined but never populated by any extractor, violating principle 7 ("80% case defines the schema"):
Symbol.signatureResolved?: string- removed.Member.kindvalues'index'and'call'- removed from the union. Can be added back with implementation when a TS call/index-signature extractor lands or a language that needs them ships.
Docs drift fixes. ARCHITECTURE.md referenced a
{{ str | tsdoc }}filter that never existed; replaced with{{ sym | summary }}(which does). Principle 2 in PHILOSOPHY.md now explicitly distinguishes "pattern-aware" (OK) from "language-idiosyncratic" (not OK), soSymbol.discriminator?is consistent with the stated rule. - Config:
@vellum-docs/cli@0.3.0
Minor Changes
-
2a9986c: Add
vellum build --watchwith per-template invalidationEdit a template or a source file and Vellum re-renders only the output files affected by the change. A docs project that wires
vellum build --watchalongside the host's dev server (Next.js, Mintlify, etc.) no longer needs a restart loop to see TSDoc edits.vellum build --watch
How invalidation works
Templates are instrumented while rendering. The
symbol(),symbols(), andmodule()globals record what each.velfile actually read:symbol(id)- the looked-upSymbolId,module(path)- the module path,symbols(query)- the verbatim query and the resulting ids.
On a source-file change, Vellum diffs the file's old vs new symbols (source-position-insensitive content hash) and re-renders templates whose recorded reads intersect the diff. Added symbols are re-matched against each template's prior queries so a new
symbols({ tag: 'foo' })result triggers the right renders.Template edits re-render only that template. Config changes tear down the watcher and re-prime. Render and extract errors log and keep the previous output on disk - the watcher stays alive.
API changes (minor - pre-1.0)
TemplateEngine.renderreturnsRenderResult, notstring. Custom engines update the return shape:// before async render(source, ctx): Promise<string> { ... } // after async render(source, ctx): Promise<RenderResult> { return { output } // or { output, reads } when ctx.reads was supplied }
SymbolIndexgainssymbolsByFileandremoveByFile. Custom index implementations need both.InMemorySymbolIndexnow tracks per-file provenance viaSymbol.source.file.matchesQuery(sym, query)is exported from@vellum-docs/core- the exact predicateInMemorySymbolIndex.symbols()uses, reusable by tooling that needs to test a single symbol against a query.New exports (
@vellum-docs/core)TemplateReads,createTemplateReads()SymbolDiff,diffSymbols,hashSymbol,mergeDiffs,emptyDiff,isEmptyDiffDependencyGraphVellum.extractLanguage,Vellum.renderTemplate,Vellum.listTemplates
New exports (
@vellum-docs/cli)runWatch,WatchCommandOptions
Watcher details
File watching uses chokidar v4 with Nuxt-style granular filtering:
node_modules,.git,.turbo,dist,build,coverage, and the resolvedoutDirare ignored anywhere in the tree, even if a user'ssources[lang].includewould otherwise cover them. Events are debounced 100ms;awaitWriteFinishhandles editor save races.Known tradeoff
Per-file TS extraction isn't meaningful in isolation (cross-file type graph), so
extractLanguagere-runs the extractor over the whole program on each batch. Invalidation still narrows the render set - which is what dominates latency for a typical docs project - but we don't yet skip extraction for unchanged files beyond the existing content-hash cache.
Patch Changes
-
3840674: Refactor CLI to citty + consola
Replaced hand-rolled argv parsing and
console.logwith two unjs libraries:- citty drives argument parsing, subcommand routing, and help generation.
vellum --helpandvellum build --helpnow produce consistent typed output instead of a static string. - consola handles every user-facing log through a shared tagged logger. Output is color-coded by level (
success,info,warn,error) with a uniform[vellum]prefix.
No breaking CLI surface changes —
vellum build,--watch,--config,--cwd,--no-strictall behave identically.--no-strictnow comes from citty's boolean-negation convention instead of being parsed by hand. - citty drives argument parsing, subcommand routing, and help generation.
-
6503a35: Philosophy audit fixes - strict-by-default, dead schema cleanup
An audit against the newly-written PHILOSOPHY.md surfaced four gaps. This changeset closes them.
Strict template rendering is now on by default. Principle 11 ("fail loudly at build time") was being violated by
throwOnUndefined: false- a template with a typo ({{ fn.doc.summaryy }}instead offn.doc.summary) silently rendered as empty string, and the docs shipped with a blank section. TheNunjucksEnginenow defaults to strict rendering: any output of an undefined value throws, which bubbles to a non-zero build exit.Opt-out paths, for the rare cases where silent fallback is wanted during migration:
- Config:
new NunjucksEngine({ strict: false }). - CLI:
vellum build --no-strict.
This is a behavior change. Templates that relied on silent-empty for undefined values will now fail. Typical patterns that are still safe:
{% if sym.members %},{{ sym.doc.summary }}(empty string is defined),{% for m in sym.members or [] %}. The patterns that will now break are the ones you wanted to know about anyway.Schema cleanup. Three dead schema fields removed - they were defined but never populated by any extractor, violating principle 7 ("80% case defines the schema"):
Symbol.signatureResolved?: string- removed.Member.kindvalues'index'and'call'- removed from the union. Can be added back with implementation when a TS call/index-signature extractor lands or a language that needs them ships.
Docs drift fixes. ARCHITECTURE.md referenced a
{{ str | tsdoc }}filter that never existed; replaced with{{ sym | summary }}(which does). Principle 2 in PHILOSOPHY.md now explicitly distinguishes "pattern-aware" (OK) from "language-idiosyncratic" (not OK), soSymbol.discriminator?is consistent with the stated rule. - Config:
-
Updated dependencies [2a9986c]
-
Updated dependencies [ad1aa14]
-
Updated dependencies [6503a35]
- @vellum-docs/core@0.3.0
@vellum-docs/profile-mintlify@0.2.4
Patch Changes
- Updated dependencies [be0adfe]
- @vellum-docs/core@0.2.4
@vellum-docs/profile-mintlify@0.2.3
Patch Changes
- @vellum-docs/core@0.2.3
@vellum-docs/profile-mintlify@0.2.2
Patch Changes
- @vellum-docs/core@0.2.2