WireKit 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-controlspointing 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 setup
nullis 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'fromstyle-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 acsp-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 callJs::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.locationreacheswindow.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 andlocale="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 wordnode. 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
startandendare 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, the
data-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.