v0.1.3
What's Changed
Fixed
Styleguide::registerBundledHelpers()no longer initializes the Twig
extension set before adding functions/filters. Previously, the
idempotency checkgetFunction(...) === nulltriggered
ExtensionSet::initExtensions()which locksaddFunction/addFilter
for the remainder of the env's life — every subsequent
addFunction(...)call raisedLogicException: Unable to add function "<name>" as extensions have already been initialized.The bug went
undetected through CI because the existing suite (Renderer / Router /
AssetServer / ComponentParser tests) didn't instantiateStyleguide
itself; only a downstream project bootstrapping the package would have
blown up at construction time. Fixed by switching to atryAdd…()
pattern that catches the duplicate-name throw (preserving the
"project pre-registration wins" override path) without doing any
pre-read that triggers initialization.
Added
Symfony\Bridge\Twig\Extension\DumpExtensionis now bundled —
Styleguide::registerBundledExtensions()registers it alongside the
existing five (Intl / String / Common / Attribute / Typography) so
consuming projects no longer need their own
$twig->addExtension(new DumpExtension(new VarCloner()))line in
the bootstrap. The earlier split ("project keeps DumpExtension to
avoid packaged production leak") turned out orthogonal to the actual
risk: a{{ dump(var) }}leaking into production is caught by the
DumpRuletwig-cs-fixerlint at commit time, not by withholding
the extension itself. New deps:symfony/twig-bridgeand
symfony/var-dumper(both versions^5.4 || ^6.2 || ^7.0, same
constraints downstream projects already used).- Bundled Twig helpers — every consuming project previously duplicated
the samecomponent_*/page_*/__/_x/_n/_nx/
merge_resizerregistrations plus theplaceholder/resizer/
format_date/custom_price_formatfilters in its ownindex.php.
The bundledmerge_resizeradds null-arg tolerance the originals
lacked: callers can pass any positional arg asnull(typically a
Twigcontent.image_xl ?? nullwhen that breakpoint's image is unset
for the given record) and the helper now silently drops it instead of
raising aTypeErroron the typed-variadic signature.
TheStyleguidebootstrap now registers all of them on the Twig env
it receives, idempotently (getFunction('foo') === nulland
getFilter('foo') === nullchecks so projects can override any
individual helper before constructing the bootstrap and their
version wins). - New optional config key on
Styleguide(...):typography_config— path to the project'stypography.yml.
When set,Parisek\Twig\TypographyExtensionis auto-registered
with that path instead of the empty default.
- Bundled deterministic SVG placeholder generator —
Parisek\Styleguide\Placeholder
(lazy-loaded via the standard PSR-4 autoloader the moment Twig
resolves theplaceholder()function or the|resizerfilter).
Previously every project carried its own near-identical ~390-line
global-function copy instatic/inc/placeholder.php; now the package
ships it as a regular namespaced class (Placeholder::generate(opts))
and unconditionally wires the matchingplaceholderTwig function +
|resizerfilter. Projects that need a tuned palette / subject set
register their ownplaceholderTwig function on the environment
before constructingStyleguide; the function-registration
idempotency check (getFunction('placeholder') === null) then
leaves the project's version in place. error_log()instead ofdump()in thecomponent_*/page_*
error-fallback paths. The previous duplicate-in-project versions
used Symfony VarDumper'sdump()because the project's env had
DumpExtension+'debug' => true; callingdump()unconditionally
in a packaged helper would leak HTML var-dump output in production.
Errors go to the server log instead.AGENTS.md+CLAUDE.mdat the repo root — full doctrine for
contributors and AI coding assistants. AGENTS.md is the shared
AgentMD contract (overview, dev commands, local-development-against-
a-consuming-project switch mechanism, repo layout, feature-addition
flow per layer, testing, release workflow, conventions). CLAUDE.md
is a thin pointer that imports AGENTS.md plus Claude-only runtime
notes — same patterntailwind-baseuses.- Component-kind iframe previews now render inside a
padding: 1.5rem
wrapper so short components (button, breadcrumb, alert badges, …)
don't sit flush against the iframe's top edge underneath the
styleguide chrome. Inline style (not a utility class) so the inset
works regardless of which CSS framework the consuming project
ships. Pages render their own full layout and are left untouched. - Toolbar above the iframe now prints a full breadcrumb —
[KOMPONENTA] Section / Component name (slug)— instead of the raw
slug alone. Section label feeds throughi18n.t('sections.<key>')
so cs/en both work; the section segment hides while the components
API is still loading to avoid an(undefined)flash. - Sidebar header now substitutes
{project.name}fromstyleguide.yaml
into the<div id="sg-project-name">placeholder at request time,
same regex-substitution pattern that already handled
<title>/<body data-project-name>/ favicon nodes. - Sidebar search input now actually filters the component list.
Previously<input>bound to a localqueryfield on thesearch
Alpine component which the sidebar's siblingx-forcouldn't read;
state now lives onAlpine.store('ui').searchQuery. The matcher
does NFKD-folded case-insensitive substring match againstname
andid, sodrobeckovafindsDrobečková navigace. Sections
whose filter result is empty hide from the sidebar; an active
search force-opens otherwise-collapsed sections. - External-link icon row above the iframe — clickable chips for
asana/figma/drupal/webfields declared in a
component's YAML metadata. NewlinkBarAlpine component reads
the parsed metadata already exposed by
ComponentParser::normaliseMetadataand hides itself when nothing
is declared. Ports the four-icon badge row from the pre-migration
in-tree styleguide. document.titlefollows the current route viaAlpine.effect—
{component name} — {project}for component / page,
{Overview label} — {project}for overview, plain
Styleguide — {project}otherwise. Re-runs when the route flips,
the components API resolves, or the locale switches; project name
is read once fromdocument.body.dataset.projectName.- Sidebar hides skeleton-only templates that have neither a
styleguide.twigsibling nor astyleguide:block in their YAML
metadata.ComponentParseralready marked these with
hasStyleguide: false; the sidebar'sbySection()just wasn't
honouring it. Components keep showing when they have either a
styleguide.twigfile or thestyleguide:YAML block. - Sidebar open / closed state persists across page reloads via
Alpine.$persist(the plugin was already in the bundle for the
per-section collapse state). LocalStorage keysg-sidebar-open
follows the existingsg-*namespace convention.
Full Changelog: v0.1.2...v0.1.3