Skip to content

v2.31.0

Choose a tag to compare

@pushery pushery released this 16 Aug 19:24
· 33 commits to main since this release
ff48f7d

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>.
    tabs assumes 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 emits aria-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: selected is a plain server-side
    boolean that drives both aria-selected and the roving tabindex, 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, Enter or Space commits — 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, so variant and orientation mean the same
    thing in either. See Tabs.

  • Brazilian Portuguese. pt is European Portuguese, and regional locales resolve
    through their base language — so pt-BR was rendering fluent Portuguese of the wrong
    variety. A Brazilian reader saw (abre num novo separador) where they expect
    (abre em uma nova aba), and A carregar where they expect Carregando. 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-BR now ships as a delta over pt — 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 null is for. Declare the
    four measured values in wirekit.fonts.fallbacks and WireKit emits the face. Empty by
    default and nothing is invented: a guessed size-adjust moves 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-nonce container binding or Vite::cspNonce(), so an application that already has one
    needs no configuration; an explicit :nonce still wins.

  • status-tiles items can state the word they report. The visible status word was derived
    from intent, 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 both danger, and both read "Critical": the
    first points at your application, the second at your monitoring. Pass status per item and
    the tile says your word, in the caption and in the screen-reader text alike.

  • stream gained replace(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. Setting text directly 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-audit no 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 to JSON.parse('…'), and JSON is
    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-audit warns 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
    of globalThis's own. So a chain can parse, resolve every identifier, run, and be rejected
    the moment it touches something global — $el.ownerDocument.location reaches
    window.location by 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:verify compares 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-audit read 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-audit judged 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's locale prop did nothing. The x-data object 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.panel and 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-root and 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-audit said "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-display rules — the exact hand workaround wirekit.fonts.display had 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. @wirekitScripts must RUN before Alpine
    starts — which its own defer guarantees 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 start and
    end are 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-label attribute, and wirekit.assets.middleware — the last
    of which the previous release's own entry had linked to a page that never named it.