Skip to content

Releases: photon-hq/vellum

@vellum-docs/profile-mintlify@0.3.0

Choose a tag to compare

@photon-action-bot photon-action-bot released this 15 Apr 03:39
4f6014e

Patch Changes

  • ad1aa14: Add cell filter + TypeString.oneline for cell-safe rendering

    Every 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("<","&lt;") | replace(">","&gt;") }}

    Two additions collapse that:

    TypeString.oneline?: string - populated at extraction time with the whitespace-collapsed form of text. Omitted when equal to text (single-line case). Fixes the \n + indentation problem at source, before any template filter runs.

    cell filter (profile-routed) - accepts a TypeString, plain string, or null. Collapses whitespace as defence-in-depth, routes through the profile's new cell(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("<","&lt;") | replace(">","&gt;") }}` | {{ m.doc.summary }} |

    After:

    | `{{ m.name }}` | {{ m.type | cell | safe }} | {{ m.doc.summary | cell | safe }} |

    Schema additions (additive)

    • TypeString.oneline?: string
    • RendererProfile.cell(value: string, ctx: RenderContext): string

    Existing extractors keep working - when oneline is absent the filter falls back to .text. Existing profiles get the new method implemented in MarkdownProfile and MintlifyProfile; third-party profiles must add a cell implementation.

    Out of scope

    jsx-prop and fenced contexts 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 of fn.doc.summary) silently rendered as empty string, and the docs shipped with a blank section. The NunjucksEngine now 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.kind values '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), so Symbol.discriminator? is consistent with the stated rule.

  • Updated dependencies [2a9986c]

  • Updated dependencies [ad1aa14]

  • Updated dependencies [6503a35]

    • @vellum-docs/core@0.3.0

@vellum-docs/profile-markdown@0.3.0

Choose a tag to compare

@photon-action-bot photon-action-bot released this 15 Apr 03:39
4f6014e

Patch Changes

  • ad1aa14: Add cell filter + TypeString.oneline for cell-safe rendering

    Every 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("<","&lt;") | replace(">","&gt;") }}

    Two additions collapse that:

    TypeString.oneline?: string - populated at extraction time with the whitespace-collapsed form of text. Omitted when equal to text (single-line case). Fixes the \n + indentation problem at source, before any template filter runs.

    cell filter (profile-routed) - accepts a TypeString, plain string, or null. Collapses whitespace as defence-in-depth, routes through the profile's new cell(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("<","&lt;") | replace(">","&gt;") }}` | {{ m.doc.summary }} |

    After:

    | `{{ m.name }}` | {{ m.type | cell | safe }} | {{ m.doc.summary | cell | safe }} |

    Schema additions (additive)

    • TypeString.oneline?: string
    • RendererProfile.cell(value: string, ctx: RenderContext): string

    Existing extractors keep working - when oneline is absent the filter falls back to .text. Existing profiles get the new method implemented in MarkdownProfile and MintlifyProfile; third-party profiles must add a cell implementation.

    Out of scope

    jsx-prop and fenced contexts 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 of fn.doc.summary) silently rendered as empty string, and the docs shipped with a blank section. The NunjucksEngine now 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.kind values '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), so Symbol.discriminator? is consistent with the stated rule.

  • Updated dependencies [2a9986c]

  • Updated dependencies [ad1aa14]

  • Updated dependencies [6503a35]

    • @vellum-docs/core@0.3.0

@vellum-docs/language-server@0.3.0

Choose a tag to compare

@photon-action-bot photon-action-bot released this 15 Apr 03:39
4f6014e

Patch Changes

  • ad1aa14: Add cell filter + TypeString.oneline for cell-safe rendering

    Every 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("<","&lt;") | replace(">","&gt;") }}

    Two additions collapse that:

    TypeString.oneline?: string - populated at extraction time with the whitespace-collapsed form of text. Omitted when equal to text (single-line case). Fixes the \n + indentation problem at source, before any template filter runs.

    cell filter (profile-routed) - accepts a TypeString, plain string, or null. Collapses whitespace as defence-in-depth, routes through the profile's new cell(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("<","&lt;") | replace(">","&gt;") }}` | {{ m.doc.summary }} |

    After:

    | `{{ m.name }}` | {{ m.type | cell | safe }} | {{ m.doc.summary | cell | safe }} |

    Schema additions (additive)

    • TypeString.oneline?: string
    • RendererProfile.cell(value: string, ctx: RenderContext): string

    Existing extractors keep working - when oneline is absent the filter falls back to .text. Existing profiles get the new method implemented in MarkdownProfile and MintlifyProfile; third-party profiles must add a cell implementation.

    Out of scope

    jsx-prop and fenced contexts 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 of fn.doc.summary) silently rendered as empty string, and the docs shipped with a blank section. The NunjucksEngine now 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.kind values '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), so Symbol.discriminator? is consistent with the stated rule.

  • Updated dependencies [2a9986c]

  • Updated dependencies [ad1aa14]

  • Updated dependencies [6503a35]

    • @vellum-docs/core@0.3.0

@vellum-docs/extractor-typescript@0.3.0

Choose a tag to compare

@photon-action-bot photon-action-bot released this 15 Apr 03:39
4f6014e

Patch Changes

  • ad1aa14: Add cell filter + TypeString.oneline for cell-safe rendering

    Every 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("<","&lt;") | replace(">","&gt;") }}

    Two additions collapse that:

    TypeString.oneline?: string - populated at extraction time with the whitespace-collapsed form of text. Omitted when equal to text (single-line case). Fixes the \n + indentation problem at source, before any template filter runs.

    cell filter (profile-routed) - accepts a TypeString, plain string, or null. Collapses whitespace as defence-in-depth, routes through the profile's new cell(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("<","&lt;") | replace(">","&gt;") }}` | {{ m.doc.summary }} |

    After:

    | `{{ m.name }}` | {{ m.type | cell | safe }} | {{ m.doc.summary | cell | safe }} |

    Schema additions (additive)

    • TypeString.oneline?: string
    • RendererProfile.cell(value: string, ctx: RenderContext): string

    Existing extractors keep working - when oneline is absent the filter falls back to .text. Existing profiles get the new method implemented in MarkdownProfile and MintlifyProfile; third-party profiles must add a cell implementation.

    Out of scope

    jsx-prop and fenced contexts 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 of fn.doc.summary) silently rendered as empty string, and the docs shipped with a blank section. The NunjucksEngine now 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.kind values '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), so Symbol.discriminator? is consistent with the stated rule.

  • Updated dependencies [2a9986c]

  • Updated dependencies [ad1aa14]

  • Updated dependencies [6503a35]

    • @vellum-docs/core@0.3.0

@vellum-docs/engine-nunjucks@0.3.0

Choose a tag to compare

@photon-action-bot photon-action-bot released this 15 Apr 03:39
4f6014e

Minor Changes

  • 2a9986c: Add vellum build --watch with per-template invalidation

    Edit 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 --watch alongside 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(), and module() globals record what each .vel file actually read:

    • symbol(id) - the looked-up SymbolId,
    • 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.render returns RenderResult, not string. 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
    }

    SymbolIndex gains symbolsByFile and removeByFile. Custom index implementations need both. InMemorySymbolIndex now tracks per-file provenance via Symbol.source.file.

    matchesQuery(sym, query) is exported from @vellum-docs/core - the exact predicate InMemorySymbolIndex.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, isEmptyDiff
    • DependencyGraph
    • Vellum.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 resolved outDir are ignored anywhere in the tree, even if a user's sources[lang].include would otherwise cover them. Events are debounced 100ms; awaitWriteFinish handles editor save races.

    Known tradeoff

    Per-file TS extraction isn't meaningful in isolation (cross-file type graph), so extractLanguage re-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 cell filter + TypeString.oneline for cell-safe rendering

    Every 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("<","&lt;") | replace(">","&gt;") }}

    Two additions collapse that:

    TypeString.oneline?: string - populated at extraction time with the whitespace-collapsed form of text. Omitted when equal to text (single-line case). Fixes the \n + indentation problem at source, before any template filter runs.

    cell filter (profile-routed) - accepts a TypeString, plain string, or null. Collapses whitespace as defence-in-depth, routes through the profile's new cell(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("<","&lt;") | replace(">","&gt;") }}` | {{ m.doc.summary }} |

    After:

    | `{{ m.name }}` | {{ m.type | cell | safe }} | {{ m.doc.summary | cell | safe }} |

    Schema additions (additive)

    • TypeString.oneline?: string
    • RendererProfile.cell(value: string, ctx: RenderContext): string

    Existing extractors keep working - when oneline is absent the filter falls back to .text. Existing profiles get the new method implemented in MarkdownProfile and MintlifyProfile; third-party profiles must add a cell implementation.

    Out of scope

    jsx-prop and fenced contexts 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 of fn.doc.summary) silently rendered as empty string, and the docs shipped with a blank section. The NunjucksEngine now 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.kind values '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), so Symbol.discriminator? is consistent with the stated rule.

  • Updated dependencies [2a9986c]

  • Updated dependencies [ad1aa14]

  • Updated dependencies [6503a35]

    • @vellum-docs/core@0.3.0

@vellum-docs/core@0.3.0

Choose a tag to compare

@photon-action-bot photon-action-bot released this 15 Apr 03:39
4f6014e

Minor Changes

  • 2a9986c: Add vellum build --watch with per-template invalidation

    Edit 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 --watch alongside 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(), and module() globals record what each .vel file actually read:

    • symbol(id) - the looked-up SymbolId,
    • 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.render returns RenderResult, not string. 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
    }

    SymbolIndex gains symbolsByFile and removeByFile. Custom index implementations need both. InMemorySymbolIndex now tracks per-file provenance via Symbol.source.file.

    matchesQuery(sym, query) is exported from @vellum-docs/core - the exact predicate InMemorySymbolIndex.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, isEmptyDiff
    • DependencyGraph
    • Vellum.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 resolved outDir are ignored anywhere in the tree, even if a user's sources[lang].include would otherwise cover them. Events are debounced 100ms; awaitWriteFinish handles editor save races.

    Known tradeoff

    Per-file TS extraction isn't meaningful in isolation (cross-file type graph), so extractLanguage re-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 cell filter + TypeString.oneline for cell-safe rendering

    Every 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("<","&lt;") | replace(">","&gt;") }}

    Two additions collapse that:

    TypeString.oneline?: string - populated at extraction time with the whitespace-collapsed form of text. Omitted when equal to text (single-line case). Fixes the \n + indentation problem at source, before any template filter runs.

    cell filter (profile-routed) - accepts a TypeString, plain string, or null. Collapses whitespace as defence-in-depth, routes through the profile's new cell(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("<","&lt;") | replace(">","&gt;") }}` | {{ m.doc.summary }} |

    After:

    | `{{ m.name }}` | {{ m.type | cell | safe }} | {{ m.doc.summary | cell | safe }} |

    Schema additions (additive)

    • TypeString.oneline?: string
    • RendererProfile.cell(value: string, ctx: RenderContext): string

    Existing extractors keep working - when oneline is absent the filter falls back to .text. Existing profiles get the new method implemented in MarkdownProfile and MintlifyProfile; third-party profiles must add a cell implementation.

    Out of scope

    jsx-prop and fenced contexts 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 of fn.doc.summary) silently rendered as empty string, and the docs shipped with a blank section. The NunjucksEngine now 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.kind values '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), so Symbol.discriminator? is consistent with the stated rule.

@vellum-docs/cli@0.3.0

Choose a tag to compare

@photon-action-bot photon-action-bot released this 15 Apr 03:39
4f6014e

Minor Changes

  • 2a9986c: Add vellum build --watch with per-template invalidation

    Edit 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 --watch alongside 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(), and module() globals record what each .vel file actually read:

    • symbol(id) - the looked-up SymbolId,
    • 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.render returns RenderResult, not string. 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
    }

    SymbolIndex gains symbolsByFile and removeByFile. Custom index implementations need both. InMemorySymbolIndex now tracks per-file provenance via Symbol.source.file.

    matchesQuery(sym, query) is exported from @vellum-docs/core - the exact predicate InMemorySymbolIndex.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, isEmptyDiff
    • DependencyGraph
    • Vellum.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 resolved outDir are ignored anywhere in the tree, even if a user's sources[lang].include would otherwise cover them. Events are debounced 100ms; awaitWriteFinish handles editor save races.

    Known tradeoff

    Per-file TS extraction isn't meaningful in isolation (cross-file type graph), so extractLanguage re-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.log with two unjs libraries:

    • citty drives argument parsing, subcommand routing, and help generation. vellum --help and vellum build --help now 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-strict all behave identically. --no-strict now comes from citty's boolean-negation convention instead of being parsed by hand.

  • 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 of fn.doc.summary) silently rendered as empty string, and the docs shipped with a blank section. The NunjucksEngine now 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.kind values '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), so Symbol.discriminator? is consistent with the stated rule.

  • Updated dependencies [2a9986c]

  • Updated dependencies [ad1aa14]

  • Updated dependencies [6503a35]

    • @vellum-docs/core@0.3.0

@vellum-docs/profile-mintlify@0.2.4

Choose a tag to compare

@photon-action-bot photon-action-bot released this 14 Apr 06:52
f2b2a12

Patch Changes

  • Updated dependencies [be0adfe]
    • @vellum-docs/core@0.2.4

@vellum-docs/profile-mintlify@0.2.3

Choose a tag to compare

@photon-action-bot photon-action-bot released this 14 Apr 05:26
118d58e

Patch Changes

  • @vellum-docs/core@0.2.3

@vellum-docs/profile-mintlify@0.2.2

Choose a tag to compare

@photon-action-bot photon-action-bot released this 14 Apr 04:54
01f9d9e

Patch Changes

  • @vellum-docs/core@0.2.2