Version 1.5.1

September 28, 2026

Fixed

  • Picking a variant in the blocks gallery could add a different one. The gallery lets an editor choose between a component's stories and inserts the one they chose — but only its text boxes and dropdowns. A Lightswitch field, the natural way to build a "reversed" or "dark" variant, keeps its value in a hidden input inside the switch, and there was nothing there to find: the arg was dropped without a word. The card previewed "Image + Text"; the click added "Text + Image". Reported by a customer the day after they started using it on a live project.

    Switches are now set whichever way the story says — through Craft's own LightSwitch API when it has attached, and by leaving the markup exactly as the server renders an "on" switch when it has not yet. Radio Buttons are matched the way Dropdowns are, encoded option values included, and Number, URL, Email and Phone fields are filled like any other text box; they were skipped for the same reason. Checkboxes, which hold several values, are still left alone.

    It went unnoticed because the plugin's own test project switches its hero theme with a Dropdown, which always worked. The fixture was ours; the field a real agency reaches for was not.

Version 1.5.0

September 21, 2026

Fixed

  • Marking a story stable could rewrite the wrong line and silently fail. The writer looked for status with a regular expression and took the first occurrence in the meta block, without checking it was a key. A description that merely mentioned the word won: clicking “mark stable” rewrote the developer's own sentence, left the real status at draft, and reported success. It now walks the meta block instead — skipping string literals, comments and anything nested — and only accepts status in key position.

    This is a file the plugin did not create and promises not to damage, so the write itself was hardened at the same time: contents go to a sibling temp file and are renamed over the original, which means the story file is either the old bytes or the new ones and never half of either. Short writes are detected too; previously a truncated write counted as success.

  • Adapters written with whitespace control were invisible. {%- include … %} — ordinary Twig, and how most adapters are actually written — was never recognised as an include, in two separate places: the tag scanner kept the leading - as the tag name, and a cheap pre-filter tested for the literal string {% include. The effect was not a wrong answer but no answer: the component had no adapter, so the contract check passed everything and the badge stayed dark. Silence is the failure this plugin exists to remove, and it had learned to produce it.

  • block.type.handle == '…' was not recognised as a type switch. Only the shorter block.type == '…' was, although .handle is the idiomatic Craft 5 spelling. Same consequence as above: the binding was dropped and every story counted as reproducible.

Changed

  • A story with no status is now treated as a draft, not as stable. It used to be addable from the blocks gallery, which read silence as a promise: a story nobody had reviewed was offered to editors as ready. Absence of a claim is not a claim.

    If you have hand-written stories with no status in their meta block, they will stop appearing as addable cards until you mark them stable — the gallery says so on the card rather than leaving you to guess. Scaffolded stories are unaffected: they have always been written with an explicit status.

Added

  • Undo, right after adding a block from the gallery. Adding a block used to be a one-way door: the panel stays open so an editor can compose a whole page in a few clicks, which also means a wrong click costs a scroll down the page, a menu and a delete. A notice now appears with the block's name and an Undo link, and taking it back runs Craft's own delete — the same call behind the block's action menu — so the result is exactly as if the block had never been added.

    This is offered in inline Matrix fields only. In Cards and Index mode Craft creates the entry on the server and opens a slideout over it, where undoing would mean deleting a saved element behind the editor's back; an Undo that works in one of the two Matrix UIs and quietly does nothing in the other is worse than no Undo at all, so nothing is shown there.

  • Filter the guide by group and by status. Search alone answers “where is X”; it does not answer “what is still draft” or “show me only this group”, which is the question on a project with forty components. Two selects next to the search do, and they compose with search rather than replacing it.

    Neither is drawn unless it has something to choose between: a lone “All groups” on a one-group project, or a status select on a project where everything is stable, are controls that do nothing. Statuses are read off the page rather than from a list the plugin invents, with two buckets a story cannot express — no status written, and no story at all — and the list is rebuilt after every “Mark stable”, so it never offers a status that matches nothing and never misses one that just appeared.

    The blocks gallery's own Group checkbox now follows the same rule: on a project with one group it grouped nothing — the heading was already suppressed — so it is no longer drawn.

  • The guide page now says what the guide is for. The intro described a browser over your templates — which is a list a developer could have generated themselves. The half worth paying for happens somewhere they never look: the gallery an editor opens on an entry. The intro now names what the editor actually gets there, in the same paragraph rather than a second one, and says something different in Lite, where the gallery opens but the click does not insert.

  • A free Lite edition. The line between Lite and Pro is not developer features versus editor features — it is seeing versus acting.

    Everything that tells you the truth about your own project is free, and that is the whole of it: the component index, marker files, live previews rendered with your site's CSS, the contract badge that catches a story promising a field the entry type does not have, loud render errors instead of a silent pink rectangle, the write journal, and the story scaffolder. A component library you cannot see is the problem this plugin exists for, and putting a price on seeing it would be a strange way to solve that.

    Pro is the plugin doing something on your behalf: writing a filled-in block into an entry from the blocks gallery, and running unattended in CI.

    The blocks gallery still opens in Lite. Editors browse the real rendered cards for the blocks a field accepts — hiding them would hide the very thing being offered — and only the click that inserts a filled-in block is held back. Craft's own "New Block" menu is untouched, so nothing an editor already had is taken away.

  • stories/check, a contract check for CI.

    php craft component-guide/stories/check
    

    Pro only. The edition is read from your committed project.yaml, so a CI runner needs no licence key of its own; under Lite the command says what it is and exits 0 rather than failing a build to advertise.

    Exits 0 when the stories, the adapters and the entry types still agree, and non-zero (65, Yii’s DATAERR) when they do not — so a CI job can simply run it. --format=json for machine reading; --fail-on to choose which findings break the build (by default, the two that mean the gallery is lying to an editor — a field the adapter never fills, and a handle the entry type does not have). An unknown --fail-on or --format stops the command rather than being ignored — a gate that cannot be made to fail is worse than no gate.

    This is the failure that outlives the people who caused it. A field gets renamed during a content-model cleanup, a field is dropped from an entry type, an adapter is rewritten. Nothing throws, every preview still renders, and the gallery goes on offering editors a state they can no longer produce — usually discovered months later by someone who did not build the library.

    It deliberately renders nothing, and that is a correctness decision rather than a shortcut. A console request is not a site request, so Twig extensions that plugins register only for site requests may not be loaded there — the same trap that broke control-panel previews on real projects until 1.3.0, and it fails at compile time, where even an unreachable branch kills the template. A pass rate produced in the wrong request context is worse than no pass rate, because it reads as verification. Comparing three lists of names has none of that exposure.

    Components with no matching entry type are not checked, and the command says so — without a block behind it there is no contract, and a clean run should not be mistaken for full coverage.

Version 1.4.1

September 17, 2026

Fixed

  • The contract badge no longer fires on a healthy component. An adapter can be written two ways. One file per block type, handed the entry as block and including the component with block.heading. Or one file for all of them, handed the parent entry, looping its Matrix field and switching on block.type — which is the shape this plugin's own setup guide recommends, and the one it greps for to find adapters at all.

    Only the first was modelled. In the second, block is a loop variable, and loop variables were deliberately skipped when working out which variable holds the block — so the root fell through to the parent entry, and every argument looked as though it came from the parent's Matrix field, which no block entry type has. Every argument was then reported as something no editor could produce: a badge on a component where nothing was wrong, and a blocks gallery offering the editor nothing at all.

    The two shapes are told apart by the type switch itself: a loop variable compared against a type handle is the block entry, not a row inside it. A genuine repeater in the same adapter is still resolved as before.

    Worth saying plainly, because it is the whole argument for the badge: a badge that fires on healthy components is worse than no badge, since it teaches people to ignore it on broken ones.

  • A story holding a live element query no longer takes down the whole section. Story arguments are kept exactly as the story file produced them, deliberately: a component built around an entry is only honestly previewed with a real one, so the README allows an element or an element query in a story you trust. But the scan result is cached, caching means serializing, and an element query carries event handlers — a handler is a closure, and serialize() refuses a closure. The cache write threw, nothing caught it, and every request to the guide ended in an uncaught exception. Not one broken preview, not one component with a badge: the entire control-panel section, gone, for one argument in one story.

    The cache is an optimization, and an optimization may not break the page. A result that will not serialize is now simply not cached, and the scan runs again on the next request.

    It is not quiet about it. Something you wrote made the guide slower, so the guide tells you which story, and that calling .one(), .all() or .url() inside the story — passing the value rather than the query — restores the cache. Found on a real client project, where the section died on the first page load; it cannot happen on a story whose arguments are plain values, which is why it survived four releases unseen.

Version 1.4.0

September 13, 2026

Added

  • The guide checks that the story, the adapter and the entry type agree. Each of the three can be written correctly on its own and still combine into a promise nobody can keep. A story shows a hero with a background photograph; the adapter dutifully passes an image if the block has one; the entry type has no image field. Nothing is broken, nothing fails to render, and the blocks gallery shows an editor a card they can never reproduce.

    Rendering cannot catch this — the preview is flawless. Name matching cannot catch it — the names line up. Only reading the adapter's {% include … with { … } only %} and comparing it with the entry type's fields can, because that include site is the one place where “this argument comes from that field” is actually written down.

    Components with a mismatch get a badge on the index, deliberately not the red error one: nothing here has failed, and if red stops meaning “broken” it stops being read. Hovering it names the argument, the field behind it, and what to do about it. It does not block promoting a component to stable — a mismatch is a thing to tell you, not a thing to stop you.

    What it does not do, stated plainly so the badge's silence can be trusted: it compares presence, not quantity, so a story showing two buttons on a block whose adapter builds one is not reported. Fields the adapter never reads are listed as information rather than a problem. Nested entries are resolved for the two shapes people actually write — |map(row => { … }) and {% for row in block.cards %} — and a repeater assembled some other way is left alone rather than guessed at. An adapter that cannot be identified, or a component included from two places that both read a block, produces no verdict at all: refusing beats guessing, the same rule as for colliding handles.

  • Editors can switch between a component's states in the blocks gallery. Writing four stories only ever served the developer before — the gallery card showed the first one and the other three may as well not have existed. A component with more than one reproducible state now carries a small dropdown in its card header, the preview follows the choice, and adding the block fills it from the state the editor was actually looking at.

Changed

  • Gallery cards preview a state an editor can reach. Previously always the first story; now the first one whose arguments all have fields behind them. On a site whose hero is built around a background image it has never had, the card stops advertising the photograph and starts showing the text-only state — which is what the page was always going to look like.

Fixed

  • Prefill knew which field to fill from a convention we invented. Adding a block from the gallery copies the story's content into the new block, and the code matched a story argument to a field by name, with one hard-coded alias: an argument ending in Html would also try a field ending in Text. That is the same defect as the old exact-name matching, one level down — it worked for projects that happened to share our habit and failed silently for everyone else, leaving an empty block and no explanation. The adapter states the real mapping; the guide reads it there now, and the browser guesses nothing. An argument assembled from two fields has no single destination, so it is left alone rather than filled from whichever field came first.

  • Dropdown fields were never prefilled at all. The selector only ever looked at text inputs and textareas, so a card previewing the dark variant of a component handed the editor the light one. Dropdowns are filled now, and only with an option the field actually offers.

Version 1.3.0

September 11, 2026

Changed

  • Entry-type matching ignores case and separators now. It used to be exact, and that was the wrong promise — not because projects are careless, but because both sides follow their own convention and the conventions disagree. Craft builds a handle from the block's name, so “Inline Donation Form” becomes inlineDonationForm; a developer names the file inline-donation-form.twig, because that is how files are named. Craft's own docs put an underscore on templates that should not be routed to, so half a project is _featured-story.twig while no handle can contain an underscore at all. Exact matching only ever found the people who already knew the rule: on the first real project this was tried on, ten templates paired one-to-one with a handle and not one of them matched.

    hero-card.twig, hero_card.twig, _hero-card.twig and heroCard.twig are all the same name to the guide now, and all match the handle heroCard. The rule is computed in one place in PHP and shipped to the blocks gallery with both the components and the entry types, so the browser never normalises anything of its own and the index cannot disagree with the gallery.

    Two things it refuses rather than guesses. When two templates in the same guide collapse to the same name, both are flagged on the index and neither is offered to editors — quietly picking whichever the scan reached last is the kind of answer this plugin exists to avoid. The same goes for two entry-type handles that collide with each other.

Fixed

  • Previews are served from a site route now, not a control-panel one. A CP request does not have the Twig extensions that other plugins register for site requests only — Formie's filters, Sprig, and plenty of project modules. A component template that used one of those did not merely misbehave: it failed to compile, because Twig resolves filters when it parses, so even a branch that never runs took the whole preview down with an “Unknown filter” error. The same cause left Sprig-backed components rendering their chrome and nothing else. The preview URL is now built in one place, so the index, the component page and the blocks gallery cannot drift apart, and the control-panel route stays registered for headless installs and for any URL somebody bookmarked. Found by running the plugin on a real client project, where five of thirteen components could not render at all.
  • A component whose preview throws now says so on its card. The index showed nothing: a failed thumbnail was a pink rectangle and everything else about the card — the description, the story count, the “Mark stable” button — looked exactly like a healthy one. The error badge existed, but only for scan errors. The index cannot know on its own, because it never renders a story (the thumbnail frames do, in the browser, and rendering every story server-side would cost a full render per card on each page load), so each preview now reports its own outcome to the page that framed it and the card turns the badge on, with the message in its tooltip.

    Two things it deliberately does not do. It does not light up for a story that rendered nothing — that is a legitimate state for a component whose guards are doing their job, and a badge that cries wolf stops being read. And it does not block “Mark stable”: a render error can come from the environment rather than the story, and a gate that can be wrong is worse than silence.

  • The index no longer calls a component ready for editors when it has nothing to show them. A story file that exists but fails to parse leaves the component “documented” — the file is there, which is what stops the scaffolder overwriting it — while holding no stories at all. Such a component was counted in “ready for editors” and chipped “in gallery”, both of which were untrue: the blocks gallery had no preview and no prefill for it and rendered it as a bare title bar, the same as any block type the project never documented. The developer was told the handoff had completed when it had not. Appearing in the gallery now means a matched entry type and at least one story that actually parsed, decided in one place so the index and the gallery cannot disagree.

Version 1.2.1

September 7, 2026

Fixed

  • The agent recipe pointed at the wrong folder. A dry run on a real project documented a folder of shared partials — pagination, sidebar boxes, form fragments — and left the page-builder blocks alone, which is the half that reaches editors. Two rules were at fault. Step 1 asked which folder holds components, and that is a question projects answer with a folder name; it now starts from the adapters instead (the templates that switch on a block type), because every template an adapter includes is a presentational component with its argument list already written out at the include site. Step 2 treated any mention of entry., block. or craft. as proof of an adapter, and so rejected six templates that an adapter already feeds with plain variables — their craft. calls were a query parameter, a config value and a helper called on an id that a story can supply. The test now asks what a template fetches; {% include … only %} is stated as proof that a template is presentational, since only cuts off the surrounding context and leaves it nothing else it could be; and a leading underscore is read in context — where nearly every file in a folder carries one, the prefix carries no information and skipping those files documents nothing.
  • The recipe's report format now asks which presentational template each block type is handed to. That map is the part a human cannot get from the file tree.
  • The recipe no longer lets an agent report a pass rate. One reported “34 of 34 render without error” and the control panel disagreed on five of them: an agent's render and the control panel's are not the same request, and a template that compiles in one can fail in the other when a Twig filter from another plugin is registered for site requests only. A number like that reads as verification and invites skipping the review pass the whole recipe exists to set up. Errors get reported with file and message; silence covers the rest.
  • Marker descriptions are prose now, not Markdown. The guide prints the paragraph as text, so backticks and asterisks showed up as characters. The recipe says to write it plainly.

Added

  • The control panel names the recipe. 1.2.0 shipped it and then never mentioned it: the empty-state panel described the one-button path only, and that panel is gone exactly when a folder is large enough for the recipe to matter. It is named in the panel now and, separately, as a hint above the list whenever more than three components have no story. Both carry the one line to hand an agent.

Version 1.2.0

September 7, 2026

Added

  • Status toggle in the control panel. A documented component that is draft gets a “Mark stable” button on its index card; a stable one gets “Back to draft”. Only those two: beta and deprecated express a developer's lifecycle decision and stay in the IDE. The edit is surgical — the one status entry inside meta changes, every other byte of the story file is left as written — and it goes through the same two gates as “Add story” (allowAdminChanges, writable templates directory), so it does not appear on read-only environments. Exists because a scaffolder or a coding agent can leave forty drafts behind, and promoting each by opening a file is the kind of chore that makes help feel like more work. Posts over fetch and repaints the card in place: a reload would throw a reviewer back to the top of the list after every single decision, which at forty components is the whole cost of the feature. The card is now a box with a stretched overlay link rather than one large <a>, so it can hold real buttons and still open the component when clicked anywhere else.
  • Status toggle on the component page too — next to the status chip, where a reviewer is already looking at the preview when they decide. The index is for the obvious ones; this is for the ones you had to open.
  • “Changed from the control panel” banner. The CP has no git status, so it now says what it wrote itself: every scaffold and every status change is journalled (in runtime storage, never in the repository), and the index lists the files until they have been dealt with. The journal verifies its own claim on every read and drops an entry the moment there is doubt — the file's hash no longer matches what was written (edited or reverted since), .git/index is newer than the file (committed since), or a person clicked “Reviewed”. A warning that can be wrong errs towards silence: a banner that lies twice is a banner nobody reads.
  • AGENT-SETUP.md — a setup recipe written for coding agents, shipped in the package root so it is on disk after composer require. Point any agent at it and it documents an existing component library: one marker file, one draft story per component, a report of blocks with no matching entry-type handle. It does not duplicate the story format (it points at the README) and never commits; the human reviews the result on the rendered previews and promotes with the new toggle.

Version 1.1.2

August 25, 2026

Changed

  • Scaffolded stories now include group, empty. The key has always worked and the README documents it, but the scaffolder never wrote it — so a developer working from a generated file had no way to learn the option exists, and the three keys it did write (title, description, status) read as the whole vocabulary. It is emitted empty rather than filled: the parser trims and drops empty values, so an empty group is identical to no group at all and the component keeps inheriting from its folder hierarchy or marker file. A real group written into every scaffold would freeze that inheritance, and renaming a marker's H1 would stop moving anything under it. The generated file now shows the full meta vocabulary with the one optional key sitting where you would type it.

Version 1.1.1

August 24, 2026

Fixed

  • The story-file hint on undocumented cards no longer breaks words mid-letter (“the” rendering as “t / he”). word-break: break-all was set on the whole card meta line so long template paths would wrap; it now applies only to the path itself, which is the part that needs it.

Changed

  • The index intro is one short sentence again. It named the entry-type matching rule, the gallery and both places editors meet it — four lines of prose before anything actionable. The rule is now reported by the page itself (the “ready for editors” count, the in gallery chips, the hint on story-less cards), so the prose no longer has to teach it.

Version 1.1.0

August 24, 2026

Added

  • The index now reports what editors actually get. A component reaches the blocks gallery only when a Matrix entry type carries its template's base name as a handle — a rule that lived in the picker's JavaScript and was invisible from the control panel, so a mismatched name made the gallery silently empty with nothing to explain it. The index now counts the components that are documented, stable and matched (“N ready for editors”), marks each one in gallery with the block name it becomes, and tells a story-less template that a story would also buy it a card in the gallery. The count is hidden entirely on projects where no component matches an entry type, so a guide that was never about page-builder blocks doesn't display a permanent zero.
  • Component pages say the same thing in one line, including the inverse: a component marked draft or deprecated states that the gallery is showing editors that block as unavailable.

Fixed

  • Scaffolding on read-only hosting no longer lies about why it failed. With allowAdminChanges on but a read-only deployed filesystem — Craft Cloud, containers, some managed hosts — the Add story button appeared, the write failed, and the message read “check filesystem permissions”, sending the developer after a chmod that does not exist. The button is now hidden wherever the templates directory isn't writable, with the same stated-reason notice the allowAdminChanges gate already used, and the scaffolder itself says the directory is read-only and that the story should be scaffolded locally and committed. Found on Craft Cloud, 24.08.2026.

Changed

  • The intro and onboarding copy name the second half of the plugin: a story file buys previews here and a card editors click to add the block.
  • New GalleryMatcher service, now the single source of the entry-type matching rule; the picker endpoint and the control panel share it instead of keeping two copies that could drift apart.

Version 1.0.1

August 18, 2026

Packaging and metadata only — nothing about the plugin's behaviour changes.

Changed

  • The distributed package no longer ships tests, docs, screenshots or static analysis config, so installing it downloads roughly 700 KB less. The repository is unchanged; this only affects what Composer pulls down.
  • composer.json gained discovery keywords (matrix, matrix blocks, page builder, blocks, block preview, live preview, authoring experience) so the package is findable by the words people actually search for rather than only the ones that describe it.
  • The bundled README now documents installing from the Plugin Store first; the 1.0.0 tag still carried the pre-release instructions.

Version 1.0.0

August 17, 2026

First stable release. The discovery and preview workflow has run on real Craft 5 projects through seven public betas; this release marks the API stable and the plugin production-ready. Everything below landed since 0.1.0-beta.3.

Added

  • The scan cache is now taggable and appears in Utilities → Caches as “Component Guide scan cache”, so it can be cleared on its own instead of forcing Craft's global “clear everything”.
  • Uninstalling drops that cache and logs what was deliberately left behind: story and marker files stay, because they are project code in git — "remove the plugin and your project is untouched" only holds if nothing deletes them. README and BETA.md now spell out what goes, what stays and what was never touched.
  • One story per state, on request. When a template switches on a value (theme == 'dark', mediaPosition == 'right'), the scaffolder can write one story per value instead of a single Default — named after the state (Light, Dark, Media right). Opt-in and self-explanatory: the buttons read Add story and Add 2 stories (however many were found, with a tooltip naming the values), and the second only appears where states exist. --states does the same from the CLI.
  • Placeholder tokens in stories. String args can now say what kind of content they need instead of carrying it: @lorem_w_6, @lorem_p_2, @image_1600x600, @icon_star. Expansion is deterministic (seeded by component + story + argument path), so previews never flicker and gallery thumbnails match the detail page, while items in a list still differ from each other. Photos fall back to an inline placeholder when the network isn't available; icons are inline Craft system icons. Unknown @… values pass through untouched.
  • The story scaffolder emits those tokens instead of baked-in "Lorem ipsum", so generated stories stay short and readable — and blocks added from the gallery are prefilled with the resolved text, not the raw token.
  • The blocks gallery now works in all Matrix view modes. Cards and Index fields are Craft.NestedElementManager instances with none of the inline mode's markup, so the picker hooks the class-level afterInit event and uses the manager's public API (settings.createAttributes, addButton(), createElement()) instead of CSS selectors — which also makes it resilient to Craft's markup changing between minors. Prefill stays inline-only: in cards/index mode Craft creates the entry server-side and opens a slideout.
  • previewTemplate is now editable in the settings screen (it was config-file only) and documented there as the recommended route for Vite/manifest builds — previously the most useful preview setting was invisible in the UI.

Changed

  • A story's viewport is now validated like status: desktop, tablet or phone, with aliases (mobile → phone, ipad → tablet, …). An unrecognised value used to be accepted and then silently ignored by the preview; it now surfaces as a scan error naming the valid options.
  • The scaffold button is now labelled Add story (singular): it always writes one story file, and the second button says how many stories go inside it.
  • Story scaffolding is gated on Craft's allowAdminChanges instead of devMode — the flag that actually means "this environment may change project files" — and where it is off the index states that plainly instead of silently hiding the button.
  • The settings screen is grouped into Discovery / Previews / Control panel sections instead of one flat list, with shorter instructions.
  • The override note is passed to Craft's form macros as a plain string (or null) rather than a macro's Markup object, so a stray newline can never render an empty phantom warning again.

Version 0.1.0-beta.3

August 4, 2026

Added

  • The blocks gallery blocks only what a developer explicitly marked: entry types whose component carries a non-stable status (draft, deprecated, …) render as disabled cards with a one-line reason. Story-less and unmatched types stay addable (empty) so the gallery never blocks normal content work; the native "New Block" menu is untouched.
  • Blocks added from the gallery are prefilled with the first story's scalar args (matched to field handles, bodyHtml → bodyText alias included), so a new block is immediately visible on the page instead of rendering empty.

Changed

  • The wip status is now called draft (canonical vocabulary: stable | beta | draft | deprecated). Existing story files keep working — wip and in progress normalize to draft as aliases; the scaffolder now writes status: 'draft'.

Fixed

  • The picker panel cooperates with Craft's overlay stack (z-index 100 plus a cg-overlay-open flag), so modal and slideout footer buttons stay reachable while the gallery is open.
  • Settings fields that are NOT overridden by config/component-guide.php no longer show a phantom empty warning icon (the override-note macro emitted stray whitespace, which Craft's form macros treat as a warning).
  • The “previews render without your site's CSS” hint no longer shows when a previewTemplate is configured — Vite/manifest asset tags injected there count as styling.

Version 0.1.0-beta.2

July 30, 2026

Added

  • Previews that render nothing now explain why instead of showing a blank frame (markup behind a condition the args don't satisfy, or a template that reads Craft data itself).
  • The story scaffolder builds stand-in hashes for variables accessed by dotted paths, so templates written against a Matrix block (block.heading) get a renderable story without refactoring — in Twig a plain hash reads the same as an element. Nested paths nest; trailing method calls are declared but can't be faked, and the scaffold says so in a note. Guessed values are context-aware, so nested paths get plausible stand-ins too.

Fixed

  • block is no longer treated as a Twig keyword by the scaffolder: in Craft page-builder templates it is almost always the Matrix block variable. {% block x %} and block('x') are still recognised as language constructs.

Version 0.1.0-beta.1

July 30, 2026

First public beta — the initial MVP.

Added

  • Marker-file discovery skips index.twig and undefined.twig — the entry point and fallback of the recommended dispatcher pattern are not components. (An explicit story file still documents them if you want it to.)
  • Onboarding empty state: with nothing discovered yet, the index explains the two ways in (marker file → instant inventory, story file → previews) using the project's actual scan path, and links to the settings screen.
  • A non-blocking notice above the grid when previewCss isn't configured, so unstyled previews read as "not set up yet" rather than "broken".
  • Story scaffolder: an "Add stories" button on undocumented cards (dev mode only) and a component-guide/components/make <id> console command generate a skeleton story from the template's variables — loop sources become sample item arrays, |default() and {% set x = x ?? … %} fallbacks become values, the first sentence of the leading {# … #} comment becomes the description, and the rest is guessed from variable names. Writes .stories.twig by default (--format=php for the PHP format), marks the result status: wip, and never overwrites an existing story file.
  • The "Blocks gallery" trigger is duplicated in the Live Preview editor pane header, so it stays reachable on long Matrix fields without scrolling to the field's bottom "New Block" row.
  • Recursive component discovery from a configurable templates directory.
  • Nested (button/button.twig + button/button.stories.php) and adjacent-file conventions.
  • Simple and rich PHP story formats, normalized to shared internal models.
  • Control-panel section: component index (grouped, searchable) and detail pages.
  • Isolated, sandboxed iframe previews with configurable front-end CSS/JS.
  • Copy-pasteable Twig {% include … with {…} only %} usage snippets.
  • Native settings screen with config/component-guide.php overrides.
  • Per-component, non-fatal error reporting.
  • component-guide:access permission gating all CP/preview actions.
  • component-guide/components/scan console command for CLI diagnostics.
  • component-guide/components/render console command that prints a story's full preview document for verifying preview configuration.
  • Unit tests for the scanner, story parser and snippet generator; PHPStan level 5.
  • Persistent scan cache keyed by a filesystem fingerprint (story-file mtimes), so it invalidates automatically when stories or templates change. Toggleable via the enableScanCache setting.
  • Marker-file discovery: drop a GUIDE.md, BLOCKS.md or COMPONENTS.md into a folder to list every Twig template in its subtree as an "undocumented" component — no story file needed. Group names mirror the folder hierarchy ("Components / Cards"): a marker's H1 replaces its own folder's name in the chain and is inherited by every component in the subtree without an explicit meta group (documented or not); the intro text below the H1 becomes the group description on the index page. Underscore-prefixed files are skipped, duplicate markers in one directory produce a non-fatal warning (GUIDE → BLOCKS → COMPONENTS precedence), and the scan-cache fingerprint tracks markers and covered templates automatically.

Changed

  • The persistent scan cache key now includes the mtimes of the scanner and story parsers, so changing that code invalidates stale entries by itself — in development and after a composer update — instead of relying on a hand-bumped version constant.
  • Plugin components are wired explicitly, guaranteeing Twig story support (*.stories.twig) is always active.
  • Component lookups by ID are indexed instead of linear scans.
  • The preview document rendering is shared between the web controller and the CLI via PreviewRenderer::renderDocument().
  • The Matrix picker's DOM observer coalesces mutation bursts into a single scan per frame, reducing overhead on busy CP pages.

Fixed

  • Ungrouped picker cards are no longer clipped/overlapping. Root cause: as DIRECT children of the panel's scroll container, Chromium sizes grid rows from the .card button's containment-affected intrinsic height (container-type: inline-size), cutting descriptions and thumbnails off — align-items: start alone did not cover it. Cards now always sit one nesting level below the scroller: ungrouped mode renders a single headingless group wrapper, mirroring the (working) grouped layout.
  • Toggling the picker's "Group" checkbox no longer reloads every preview iframe: cards are moved atomically (moveBefore(), with an appendChild fallback), thumbnail sizing ignores the intermediate about:blank load, and all thumbnails are re-measured after the re-layout.
  • Preview CSS/JS settings saved from the CP form are normalized to arrays within the same request.
  • The repeated folder/name component-ID collapse is now case-insensitive.