v2.31.0
Minor — a Brazilian Portuguese catalog, a font of your own that no longer moves the page, a CSP audit you can act on without checking it first, and six pages that were teaching the version before this one.
Nothing here changes what an unchanged call site renders. Two commands report more than
they used to: wirekit:csp-audit gained a warning class that leaves your exit code alone,
and wirekit:verify compares every published bundle instead of four.
Added
-
A tab bar the server drives, with no panels —
<x-wirekit::tabs.list>and
<x-wirekit::tabs.tab>.tabsassumes the browser already holds each panel's
content, which is the wrong shape for the arrangement a Livewire application reaches
for first: a row of tabs above content the server renders, where choosing one is a
round trip. Two of the things the full component does are actively wrong there — it
holds the selection your server has already decided, and it emitsaria-controls
pointing at panels that do not exist, sending a screen-reader user somewhere there is
nothing. The bar on its own holds nothing at all:selectedis a plain server-side
boolean that drives botharia-selectedand the rovingtabindex, and the keyboard
handlers resolve the tabs from the DOM on every keypress, so Livewire replacing the
markup cannot leave focus pointing at an element that no longer exists. Activation is
manual — arrows move focus,EnterorSpacecommits — because selection following
focus would fire one request per arrow key and render four pages nobody asked to see.
Both bars are styled from one source, sovariantandorientationmean the same
thing in either. See Tabs. -
Brazilian Portuguese.
ptis European Portuguese, and regional locales resolve
through their base language — sopt-BRwas rendering fluent Portuguese of the wrong
variety. A Brazilian reader saw(abre num novo separador)where they expect
(abre em uma nova aba), andA carregarwhere they expectCarregando. Nothing failed:
the page rendered, the screen reader read it out, and only a native speaker looking at the
right string would ever have caught it.pt-BRnow ships as a delta overpt— it holds
only the strings the two varieties spell differently, so the shared wording stays in one
place and cannot drift apart. Localization names
the variety of every catalog, so you can decide deliberately rather than inherit one. -
A font you host yourself can get a metric-matched fallback. Every bundled family ships
one — a local system font registered under the family's own name with the web font's
measured metrics, so text painted before the swap occupies the same box as text painted
after it. Your own family got none of that, which is the setupnullis for. Declare the
four measured values inwirekit.fonts.fallbacksand WireKit emits the face. Empty by
default and nothing is invented: a guessedsize-adjustmoves the layout in the other
direction and looks deliberate while doing it. The method, including the two parts that are
easy to get wrong, is on the Fonts page. -
The font component's inline
<style>can carry a CSP nonce. It is the only inline style
WireKit emits, and it has to stay inline — the three custom properties come from your font
configuration, not from our build. Without a nonce you cannot drop'unsafe-inline'from
style-src, and CSP Level 2 makes that a cliff rather than a slope: a nonce anywhere in a
directive makes the browser ignore'unsafe-inline'in that same directive, so this block
loses its permission the moment you add a nonce for anything else. Silently — the page
renders and your typography falls back to the system font. The value resolves itself from a
csp-noncecontainer binding orVite::cspNonce(), so an application that already has one
needs no configuration; an explicit:noncestill wins. -
status-tilesitems can state the word they report. The visible status word was derived
fromintent, which has five values — so a domain with more states folds two onto one word,
and the pair it folds is usually the one carrying the information. A health check that ran
and found a problem and one that crashed are bothdanger, and both read "Critical": the
first points at your application, the second at your monitoring. Passstatusper item and
the tile says your word, in the caption and in the screen-reader text alike. -
streamgainedreplace(text).push()cannot express a rendering derived from the
whole stream rather than accumulated from its parts — masked text where a placeholder breaks
across a chunk boundary, incremental Markdown where a closing fence changes what came before
it, anything that diffs. Settingtextdirectly looks like the workaround and quietly skips
the reduced-motion buffer;replace()takes the same path a push takes. -
The sidebar item's icon size can be personalized. It was a literal in the render call,
so the only way to resize it was to take over the surrounding block — and a taken-over block
stops inheriting improvements silently, which is the one outcome the closure form exists to
avoid.
Changed
-
wirekit:csp-auditno longer reports a bare PASS for an expression it did not fully
measure. Blade renders before Alpine reads, so an attribute carrying{{ … }}is checked
with an identifier standing in for the part the audit cannot see. That substitution was
invisible, and it hid the case that matters:Js::from()— Laravel's attribute-safe encoder,
and the documented way to hand data to Alpine — renders toJSON.parse('…'), andJSONis
precisely the name the CSP evaluator cannot resolve. The audit rejected that call written out
by hand and passed it written the way everybody writes it, in the same file on the same line.
A run now counts the expressions resting on a substitution, names the ones that call
Js::from()or@js()so you can look at those first, and qualifies its verdict instead of
claiming everything resolves in scope. Your exit code does not change — the same encoder emits
a plain literal for a number, a boolean,[]or{}, and a violation that turns out to be
nothing costs the next hundred their credibility.
See CLI reference. -
wirekit:csp-auditwarns when an expression resolves but may not evaluate. Alpine's CSP
evaluator refuses a value, not a name: it throws on any property access whose result is one
ofglobalThis's own. So a chain can parse, resolve every identifier, run, and be rejected
the moment it touches something global —$el.ownerDocument.locationreaches
window.locationby another route. Reported as a warning that leaves your exit code alone,
because the rule approximates a question about runtime values. The output also names what it
measured, so a pass is not read as more than it is, and the
CLI reference documents the supported shape. -
wirekit:verifycompares every published bundle, and reports a config option by name.
The freshness check compared four; every bundle added since inherited nothing, so an upgrade
could leave six stale files behind a clean report. It is derived from the package now. The
config check also names the missing options and prints each default instead of counting
them, and reports an option your file still carries that this version no longer offers.
Fixed
-
wirekit:csp-auditread a bare:on a<livewire:…>tag as an Alpine expression. On a
component tag that is Blade's prop binding and its value is PHP, finished on the server. The
scanner knew that for<x-…>only. Measured in a real application: 13 of 18 reported
violations were false, and 12 of those were this one construct. -
wirekit:csp-auditjudged a Livewire action as written, not as Livewire presents it.
wire:click="confirm"is rewritten to a call on the component proxy before Alpine sees it,
so a method whose name collides with a browser global was reported as a dead handler while
working perfectly. -
countdown'slocaleprop did nothing. Thex-dataobject declared the key twice, and
a JavaScript object literal keeps the last one — so the line that read the prop was dead and
locale="fr-FR"on a German page counted in German. -
The unknown-prop check now reaches sub-components. It covered the top level and none of
the 76 sub-component views —accordion.item,table.th,list.item,field.*,
dropdown.paneland the rest, which are exactly the tags you write in a loop. -
An overlay container you write yourself is finished, not just adopted. WireKit creates
#wk-overlay-rootand names it in the page's language; one already in your markup was
handed straight back without a role or a label. Writing it yourself is the sturdier route —
it sits in the markup Livewire morphs — so the localization was reaching everyone except the
readers who had built the better setup. A label you wrote is left alone. -
wirekit:csp-auditsaid "bridge" when it meant "node". A missing executable does not
throw, so the branch written for it never ran and the message never contained the word
node. It now says so before the process starts, and the dependency is named in the
command's own description.
Documentation
-
Six pages were teaching the version before the one that linked them. All six had the same
shape: the reference table was maintained and the prose around it was not. -
Fonts recommended writing your own
font-displayrules — the exact hand workaroundwirekit.fonts.displayhad replaced, and
rules that land on the same faces as the metric-matched fallbacks that are the actual fix. -
The CLI reference framed the CSP audit as a
grammar check alone, which is precisely the half that does not explain why a passing build
had started failing. It now says what the command looks at, and what it deliberately does
not. -
The script-order rule named the wrong invariant.
@wirekitScriptsmust RUN before Alpine
starts — which its owndeferguarantees in a Livewire layout, so the order of the two
directives is not the lever. The troubleshooting table sent you to check a tag order that was
already correct, ruling out the wrong hypothesis and keeping the right one. -
Scroll area documents that
startand
endare the reading edges, so a horizontal fade follows the document direction. -
Table documents the edge hint that tells a
reader the row continues before they touch it. -
Integration documents the overlay
container, thedata-wk-overlay-labelattribute, andwirekit.assets.middleware— the last
of which the previous release's own entry had linked to a page that never named it.