Changelog
Atom feedAll notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.5.1
Fixed
- Prose links now take
--primary, which is what the documentation has always said.theming.mdand the README both describedtheme.accentas recolouring links, andPresetContrastTestheld--primaryagainst--backgroundat 4.5:1 under the label "link", but links were--foregroundand--primaryonly reached a highlighted-line background, a border and the code tab underline. Setting an accent had no effect on link colour. Measured on rendered pixels after the change: neutral 18.04:1 light and 18.32:1 dark, ocean 12.31 and 13.90, laravel 4.54 and 5.54. Neutral's--primaryis all but black, so that preset looks unchanged
Changed
- Code blocks and table wrappers use the same thin, rounded scrollbar as the sidebar instead of the browser default, 6px rather than the sidebar's 8px since they sit inside content rather than beside it
0.5.0
0.5 stability freeze. Config keys and frontmatter names do not change until 1.0.
Security
- Path traversal in the docs slug. A request like
/docs/..%2FREADMEresolved outside the content root, so any.mdon disk could be read through the page route and the raw Markdown route, and compiling it wrote a PHP file outside the cache directory. Slugs with empty,.or..segments are now refused, a resolved path is checked against the content root withrealpath, andCompiledStorerefuses to build a path for an unsafe slug - Blade execution through component attributes. Attribute values were interpolated into the template string, and
e()escapes quotes but not braces, so<x-vellum::callout title="{{ php_uname() }}">ran. Values are now bound and passed as data, so docs attributes stay literal as documented - Gated content readable through the asset route.
/{prefix}/_vellum/files/served anything undervellum.path, including gated pages,meta.json,_meta.mdand dotfiles. It now serves an allowlist of asset extensions only. Assets themselves are still not gated, which is documented on the gating page
Added
- Package docs for why Vellum, versions, gating, search drivers, changelog, export, theming, and the 0.2 to 0.5 upgrade path
laravelcolour preset, matching laravel.com/docs warm sand neutrals and the Laravel redtheme.accent: a brand colour applied to links, buttons and the focus ring on any preset, as one value or['light' => ..., 'dark' => ...]. Hex,hsl()andoklch()are accepted, the button label colour is derived from it, and an unparseable value is ignoredVellum\Support\Color: colour parsing, relative luminance and WCAG contrast- Contrast test over the real stylesheets: every shipped preset clears WCAG AA (4.5:1) for body text, secondary text, links and button labels, in both modes
CONTRIBUTING.md,SECURITY.md, GitHub issue and pull request templatesversions.labels: switcher display names (1.x (LTS),Next) while folders and URLs stay the list slug- Docs for the features that shipped without any: Markdown, code blocks, images, navigation, an artisan command reference, page actions, troubleshooting, and credits
- Canonical link, Open Graph and Twitter card tags on every page. Canonical is omitted rather than guessed when
app.urlis not an origin, and a static export prefersexport.base_urlwhen that names one vellum:exportwrites a404.htmlat the export root, with root-relative asset paths so it works for a miss at any depth- A Vellum error page for content that cannot render, instead of the framework's generic 500. The reason and file appear only when
APP_DEBUGis on UnknownDirectiveException: a:::typo now names the directive and the file instead of raisingNoMatchingRendererExceptionfor an internal classDuplicateSlugException:vellum:buildrefuses to build when two files resolve to one URL, and names both.gitattributeswithexport-ignore, taking the released archive from 1860 KB to 830 KB
Changed
- Colour presets retuned to clear WCAG AA in light mode. Neutral's secondary text was 4.21:1, Laravel's link and button colours 3.96:1
- Laravel preset uses
#e32c03for text and fills and keeps#f53003for the active wash and the focus ring, the way laravel.com does - Code blocks take the shared
--mutedsurface instead of a hard-coded white and#191919, so they match the page in every preset - The sidebar takes the shared
--cardsurface, instead of a tint on some presets and the page background on others - Surfaces follow one rule in every preset and both modes:
--backgroundis the canvas at the light or dark extreme,--cardis one step toward mid-grey for the sidebar, callouts and popovers, and--mutedis a second step for code blocks, table headers, tab strips and step markers. Surfaces only ever move away from the canvas, darker in light and lighter in dark, so prose sits on the cleanest area and nesting reads correctly - Light mode canvases are near-white and dark canvases near-black, replacing a mid-grey page with lighter elements sitting on top of it
- Light-mode surfaces are lighter: the code block sat at 1.19:1 from the canvas, roughly twice the step GitHub, VitePress and Fumadocs use, and is now 1.08:1
- Code tabs are tighter: the active underline sits closer to the label, the header is 39px instead of 49px, and the code is inset by 0.25rem instead of 0.5rem
- Code tabs take the same two surfaces as everything else,
--cardfor the frame and--mutedfor the code, instead of a hard-coded grey frame around a pure white block - Syntax highlighting is more colourful. Strings are green, numbers teal, types blue, variables orange, keywords red, and every token clears 4.5:1 on every preset's code surface in both modes
- Neutral's
--mutedwas 1.003:1 against the page in light mode, so table headers, tab strips and step markers had no visible surface - Markdown tabs and UI tabs share
vellumTabs()for arrow keys, Home/End, andaria-controls - Search dialog labelled for assistive tech (
combobox+listbox); version switcher menu has Home/End andaria-controls - The active code tab label uses
--foregroundand the accent carries the underline. With the accent the docs use as an example it measured 1.42:1 against a documented 4.5:1, and now measures 18.80:1 in light and 15.04:1 in dark - The asset route sends
max-age=86400, must-revalidateinstead of a year ofimmutable, since those URLs carry no content hash and a replaced image was stale for a year guestandauthare matched without regard to case, soaccess: Authworks. Gate names are still passed toGate::allows()exactly as written
Fixed
access: Authhid a page from everyone. A capitalised value fell through toGate::allows('Auth'), false for guests and signed-in readers alike, with no error anywhere- A UTF-8 BOM before the opening
---made the whole frontmatter block parse as body text, and the title fall back to the filename - Images inside a version folder resolved against the global docs root and 404d. Each version now renders through its own pipeline and the emitted URL carries the version segment
- A page whose title fell back to its first heading shipped two
h1s, since the layout renders one of its own vellum:install --forcenever republished the config; the flag was ignored- An impossible changelog date such as
2026-13-45passed into the Atom feed, which readers reject. Dates are validated and fall back to the file mtime - An unquoted numeric frontmatter
title, which YAML parses as an int, was discarded in favour of the filename - The checked-checkbox tick had its colour baked into a data URL, which had drifted from
--muted-foregroundin light mode and never matched theoceanorlaravelpresets at all - The last sidebar item sat against the footer bar with no clearance
- The index page title rendered as "Vellum · Vellum" when the page title equalled the site name
- A long URL or identifier in inline backticks had no break opportunity and pushed the page wider than a phone viewport. It wraps now
vellum:buildwarns when a page is shadowed by the changelog route
Removed
- Colour presets
black,vitepress,dusk,catppuccin,purple,solar,emerald,rubyandaspen. Most changed one accent colour, whichtheme.accentnow does on any preset; several could not reach WCAG AA in light mode without losing the character that named them. A removed name falls back toneutralandvellum:buildwarns once - Cloudflare Workers demo (
bin/build-demo,wrangler.toml, committeddemo-dist). The public site is now a Laravel app that servesdocs/through the package
0.4.0
Versioned docs URLs and search drivers.
Added
php artisan vellum:indexrebuilds the MiniSearch compile cache and, whensearch.driverisscout, syncs Laravel Scout- Page
accessfrontmatter (guest,auth, or a gate name). Foldermeta.jsonor_meta.mdinherits downward; a page overrides. Export logs each dropped gated page
Changed
- When
vellum.versions.enabledis true, the latest version is served at/docs/.... Older versions stay at/docs/{version}/..../docs/{latest}/...redirects to the unprefixed URL with HTTP 301. The version switcher labels the latest folder "Latest". - Search is a driver (
vellum.search.driver): MiniSearch by default, Laravel Scout optional. The live MiniSearch index is a package route that filters pages the current user cannot access, cached per visibility set, with an ETag somust-revalidatecan 304. Static export always writes MiniSearch JSON and drops gated pages.
0.3.0
Markdown plus components. Pre-1.0: the API may change.
Added
<x-…>component islands in Markdown. Slots are Markdown and may nest. Attributes are quoted strings only: no{{ }}, Blade directives, or@phpin docs. Tags inside fenced or inline code stay as text- Host Blade components via
vellum.components.namespaces(default['vellum']allows<x-vellum::…>only; add''or'app'for<x-alert>). Unknown or disallowed tags fail locally and at build - Built-in
:::callout,:::tabs,:::steps, and:::cardsrender the same views as<x-vellum::callout>, tabs, steps, and cards. Prefer:::for those <x-vellum::env />,config, androutevalue tags, gated byvellum.components.allowlist(empty lists refuse every key). They resolve when the page is rendered, including inside:::tabs, not when Markdown is compiled- Request-time fragment cache for those islands, cleared by
vellum:clearandoptimize:clear - Changelog page at
/docs/changelogand Atom feed at/docs/changelog.atom(vellum.changelog.path, defaultCHANGELOG.md). Setpathtonullto disable.[Unreleased]stays in the source file; the page hides it unlessvellum.changelog.unreleasedis true; the feed never includes it - Package docs in
docs/(the Cloudflare demo exports that tree).vellum:installstubs stay a short getting-started plus one page per built-in
0.2.0
Docs chrome and Markdown polish. Pre-1.0: the API may change.
Added
- Footnotes via CommonMark, with styled list and backlinks
vellum.layout.search:sidebar(default) orheader- Collapsible desktop sidebar, persisted in
localStorage, with a floating expand/search pill, width animation, and a rounded left-edge hover peek - Mobile "On this page" popover: sticky bar, reading-progress circle, overlay rail dropdown (
max-h-[50vh]) - Page actions: copy Markdown, Open menu (ChatGPT, Claude, raw Markdown, Edit on GitHub)
- Raw Markdown route
/docs/_vellum/raw/{slug}.md, included invellum:export successandideacallout aliases (map to tip and note)vellum.theme.preset: Fumadocs palettes (neutral,black,vitepress,dusk,catppuccin,ocean,purple,solar,emerald,ruby,aspen)
Fixed
- Collapsed sidebar peek only opens from the left-edge hotzone after a short cooldown, and hides when the pointer leaves
- Theme menu in the sidebar footer opens upward so it stays on screen
bin/build-demoregisters the package throughtestbench.yamlsovellum:exportis available
Changed
- Plain inline code uses the same chip as tagged inline code
- Line numbers are CSS counters (
user-select: none) and are omitted from copied text - Sidebar item text aligns with the brand; nested items indent from that edge
- Default chrome has no top header; search and theme live in the sidebar
- Copyright footer removed; footer is an empty optional slot
- Previous/next cards use arrows, a bold title, and the page description; they sit after footnotes, half width when both exist, full width for a single direction, stacked on small screens
- Content stays left when there is no table of contents; the TOC column is still reserved
- Last-updated date sits above the H1 next to breadcrumbs
- Tighter content-column padding
- Hotkey chip defaults to
Ctrland switches to⌘on Apple platforms after hydration - TOC uses a continuous SVG rail with heading-level jogs, a 420ms active clip, and a path-following thumb
- Heading permalinks are a hover/focus copy-link control (no visible
#) - Code copy control is icon-only; title bar uses Phosphor language file marks
- Chrome icons use Phosphor (search, theme, sidebar, carets, GitHub, heading permalinks)
- TOC active and hovered section titles are semibold
- Fenced code uses GitHub Light/Dark token colours on a Fumadocs-like card surface
- Code tabs sit in a lighter grey chrome; the inner fence uses the same surface as standalone code blocks
- Author
---rules are lighter; theme no longer inserts rules between headings :::cardsrender two per row (one on mobile)- Tempest token classes are styled in the built CSS
- Callouts: rounded card, inset rounded-pill accent rail, Phosphor fill icons aligned to the title/first line, bold title, muted body
- Mobile navigation is a Fumadocs-style right sheet: 85% width, sun/moon header, a heavily blurred page overlay, and the same 280ms sidebar slide
- Mobile "On this page" popover shares one frosted surface with the title bar so the blur matches
- Neutral palette matches Fumadocs (soft gray light, charcoal dark instead of OLED black)
- Copy on embedded/tab code sits on the first line
- Docs images align to the content edge
- Task-list checkboxes use muted-foreground grey so the check stays visible
Removed
- Default copyright / "Built with Vellum" footer content
0.1.0
Initial public release. Pre-1.0: the API may change.
Added
- Laravel package that serves documentation from Markdown (
resources/docsby default); supports Laravel 11, 12, and 13 vellum:installto publish config, starter stubs, and public dist assetsvellum:build/vellum:clearfor an OPcache-friendly compile cache- Markdown extensions: callouts, tabs, steps, cards, fenced code blocks, inline code highlighting, images, external links, heading anchors
- Docs UI: sidebar navigation, table of contents, breadcrumbs, search (MiniSearch), light/dark/system theme
- Optional version folders with latest redirect and version switcher
vellum:exportfor static HTML output suitable for GitHub Pages or any static host- Config surface in
config/vellum.php(path, routes, versions, theme, search, cache, export) - GitHub Actions CI (Pest on Laravel 11/12/13, Pint, PHPStan, frontend build / size gate)
- Demo site workflow that exports package stubs for Cloudflare Workers (
bin/build-demo,wrangler.toml)
Publishing note
Tag v0.1.0 and push the tag, then submit https://github.com/VerburgtJimmy/vellum on Packagist if the package is not listed yet. Do not hardcode "version" in composer.json.