Repository navigation
v0.6.13
Upgrading
- kit v0.6.10–v0.6.12 shipped unresolved conflict markers in
Kit/Layout/Page.vue; re-runkit:update applyforcore(re-pinnedsha256:ce15e8de…e8dd→sha256:c97858e5…41da). The shipped file is a known prior version (history.json records it), so an untouched copy is replaced cleanly. A host that hand-resolved the markers sees its copy as edited: take the release bytes, or keep its resolution if it matches the one below. - A host that re-shares the kit's props in its browser test case after pest-plugin-browser's flush (a
BrowserCaseworkaround) can drop that re-share: the kit puts its props back on every web request. - Run
kit:update plan|applyforcore(re-pinnedsha256:f3932382…7dbe→sha256:6111274d…1cc8):Kit/Workbench/WorkbenchNavigator.vue,Kit/Workbench/Workbench.vueandKit/Workbench/workbench.tstake the item menus below. An item withoutactionsrenders as before. - Run
kit:update plan|applyforshell(sha256:56acbf30…3bb0→sha256:efd6116b…ba5e): the Workspace Layout and its partials take the changes below, and FocusShell is new. - Run
kit:update plan|applyforcore(re-pinnedsha256:6111274d…1cc8→sha256:2646246d…77fe),shell(sha256:efd6116b…ba5e→sha256:6b2a9dac…5077),session-ui(sha256:be81f458…6f4f→sha256:71617f5d…3108) andworkspace-ui(sha256:93e9b61a…7bed→sha256:2f226d38…39e7):css/kit.cssadds the--destructive-texttoken, and every kit stub that coloured text withtext-destructivenow usestext-destructive-text(the menus' destructive items,Kit/Form/Repeater.vue's Remove item,Kit/Overlay/Dialog.vue's danger icon, the form and page error messages,Kit/Action/HeaderStatus.vue,Kit/Search/SearchBox.vue,Kit/Workbench/WorkbenchActions.vue, the login error, the workspace switcher's Leave item and the People page's expired date). - A host with its own theme (not importing
kit.css) must define--destructive-textfor light and dark; the kit's AA values are a starting point:hsl(0 72% 42%)light andhsl(0 91% 71%)dark. Until it does, the kit's theme host step reads unmet. A host theme's value wins over the kit's, as for the other tokens. - A host that builds navigator items on the server: send
actionson the PHPWorkbenchItem(a list ofAction) to get row menus, and regenerate or re-copytypes/kit.d.ts, whoseWorkbenchItemnow hasactions. A host that sends plain arrays can switch toWorkbenchItemunchanged. - Run
kit:update plan|applyforcore(re-pinnedsha256:2646246d…77fe→sha256:e0090454…0845):Kit/Form/FormStatus.vueandKit/contract.tstake the FormStatus fix below, andFormStatustakes an optionalflashKey. Pages that pass noflashKeykeep reading thestatuspage prop. - An app that flashes its own confirmation with
Inertia::flash()can show it with<FormStatus flash-key="notice" />: Inertia v3 keeps flash data atusePage().flash, not inpage.props. Fortify'sstatusstays a shared prop and needs no change. - Run
kit:update plan|applyforcore(re-pinnedsha256:e0090454…0845→sha256:93b89e36…51e0):Kit/Workbench/Workbench.vuetakes the slot and the phone layout fix below. - Run
kit:update plan|applyforcore(re-pinnedsha256:93b89e36…51e0→sha256:ca1f9248…7179):Kit/http.tsis new, andKit/Workbench/useWorkbench.ts,Kit/Workbench/workbenchLayout.tsandKit/Feedback/passkeyOffer.tstake the fix below. A host whose own code readsresponse.data.xfrom Inertia v3'shttpclient can read it throughresponseJson(response)from@/components/Kit/httpinstead. - Run
kit:update plan|applyforcore(re-pinnedsha256:ca1f9248…7179→sha256:ce15e8de…e8dd):Kit/Action/actionPlacement.ts,Kit/Action/ActionMenuItems.vue,Kit/Workbench/Workbench.vue,Kit/Workbench/WorkbenchNavigator.vueandKit/Workbench/workbench.tstake the checkbox menu items and#navigator-itembelow. ActionMenuItems now importsDropdownMenuCheckboxItemfrom the host's shadcn-vuedropdown-menu(DataTable already did). A descriptor withoutcheckedand a Workbench without#navigator-itemrender as before. - No changes needed. A host that relied on its
HandleInertiaRequests' position for the request memo, or that wraps a kit prop in its own share, keeps working. A workaround that moved kit middleware earlier (such asprependToPriorityListforPrepareKitRequest) can be removed. - Run
kit:update plan|applyforcore(re-pinnedsha256:c97858e5…41da→sha256:12a237c5…e9f8):Kit/Form/InputCheckbox.vue,Kit/Form/Select.vue,Kit/Form/FileInput.vue,Kit/Form/RadioCardGroup.vue,Kit/Form/InputRichText.vue,Kit/Form/InputSelect.vue,Kit/Form/InputNumber.vueandKit/contract.tstake the attribute fix below. InputCheckbox's row is now wrapped in a grid (for itsdescription), so a host that styled its row by structure should check it. - Run
kit:update plan|applyforcore(re-pinnedsha256:12a237c5…e9f8→sha256:da818a98…6ef1):Kit/Layout/StickyBar.vue,Kit/Layout/stickyBars.ts,Kit/Workbench/Workbench.vueandKit/Workbench/WorkbenchActions.vuetake the fix below.StickyBartakes an optionalpinned. - A host with its own
homeroute or its own invitation flow can now keep it. Setkit.home_name(for exampleapp.home) to rename the kit's home route, and setkit.routes.session-ui.invitations => falseto leave out the kit's invitation link (transitional; see docs/catalog.md). Nothing changes by default. - Run
kit:update plan|applyforcore(re-pinnedsha256:da818a98…6ef1→sha256:fa5d0296…4815):Kit/Workbench/WorkbenchNavigator.vue,Kit/Workbench/Workbench.vueandKit/Workbench/workbench.tstake the thumbnails and filmstrip layout below. An item withoutthumbnailin the defaultlistlayout renders as before. A host that copiestypes/kit.d.tsre-copies it:WorkbenchItemhasthumbnail, andWorkbenchThumbnailis new. - Run
kit:update plan|applyforcore(re-pinnedsha256:fa5d0296…4815→sha256:ea1d2e42…3b8c):Kit/Form/SegmentedToggle.vueis new, andKit/index.ts,Kit/contract.tsandKit/Layout/PaneTabs.vuetake the changes below. PaneTabs looks different: its pressed pane is no longer a primary-filled button but a neutral raised segment in a muted track. Its semantics, props and events are unchanged. A host that styled around the old primary button, or asserted its classes, should check its phone tabs.
Added
- Workbench navigator thumbnails and a filmstrip layout (docs/workbench.md › Thumbnails and filmstrip). A navigator item takes an optional
thumbnail({ src, alt }, exported asWorkbenchThumbnail;altis required,''for a decorative image that repeats the label), rendered as a lazy, async-decoded<img>inside the item'srole="option"row in a fixed-aspect box, so the row doesn't shift when it loads.navigator-layout="filmstrip"on the Workbench (layouton WorkbenchNavigator) lists the items as a Slides-like editor's left rail: the item's number, a 16:9 thumbnail (an empty placeholder box without one) and the label;liststays the default.#navigator-thumbnail({ item, index, selected }, typed asWorkbenchNavigatorThumbnailSlotProps;thumbnailon WorkbenchNavigator) fills each filmstrip box with the host's own render, such as a scaled live slide, ahead ofthumbnail.srcand the placeholder. Its wrapper isinertandaria-hidden, and the box hascontain: strictwith overflow hidden, so the render can't take focus, change the option's name or move the layout. List rows take no thumbnail slot. The listbox semantics, keys, Alt+↑/↓ reorder, drag, filter, F6,#navigator-item(the label part) and the row menus beside the listbox are unchanged. The PHPWorkbenchItemtakesthumbnail(a newWorkbenchThumbnaildata class, default null) andtypes/kit.d.tsis regenerated; the MCPWorkbenchReadToolstill gives exactly{ id, label, type, description }, leaving the thumbnail out.composer test:workbench-item-paritycovers the new key through the Inertia round trip, and the browser job checks a filmstrip: images inside the options with their alt, nothing focusable in the listbox, Alt+↓ reorder, filter and F6, axe clean with color-contrast, each thumbnail box sized before its image loads, and a live#navigator-thumbnailholding a button and a link: inside the box at the image box's size, never focused or tabbed to, the option named by its label only, axe clean, and Alt+↓ working. - Checkbox menu items (docs/workbench.md › Item menus). An action descriptor with
checked(true or false) renders in ActionMenuItems, and so in every kit menu built on it including the Workbench row menu, asrole="menuitemcheckbox"witharia-checkedand the host's check indicator (shadcn-vue's DropdownMenuCheckboxItem). Choosing it emits the action as today; the host flipschecked. Withoutchecked, or withnull, the row stays a plain menuitem. The PHPActiontakeschecked(nullable bool, default null), andtypes/kit.d.tsis regenerated. A row menu opened from the item focuses its first row, checkbox rows included. - Workbench
#navigator-itemslot (docs/workbench.md › Custom rows). The Workbench forwards WorkbenchNavigator'sitemslot as#navigator-item({ item, selected, index }, typed asWorkbenchNavigatorItemSlotProps) in every layout: columns, the lg drawer layout, phone tabs and the overlay navigator. A host customises row content without replacing the navigator. The kit keeps therole="option"row, its keys and the row menus outside the listbox; the slot holds content only. WorkbenchNavigator now declares itsitemslot withdefineSlots. The browser job checks a row menu with a checked and an unchecked item and a Workbench using#navigator-item, with axe clean on both. - SegmentedToggle, a neutral view switch (docs/segmented-toggle.md).
Kit/Form/SegmentedToggle.vuepicks one of a few views ("Slides | Outline", grid or list) withv-model,options({ value, label, icon?, disabled? }),labelandsize. Its selected segment is a borderedbg-backgroundsurface in abg-mutedtrack, never brand-filled. It's arole="radiogroup"ofrole="radio"buttons with one tab stop: the arrows move and select, Home and End jump, and disabled options are skipped. The selected border keeps 3:1 against the track and the segment in light and dark.tests/js/segmented-toggle.test.mjschecks the source and the ratios; the browser job drives the keys, compares the selected background with the primary colour and runs axe with colour contrast, light and dark. - Workbench navigator item menus (docs/workbench.md › Item menus). A navigator item can carry its own kit action descriptors in
actions(Move up, Move down, Duplicate, Delete, as the host decides per item), and gets a row menu: a button named "Actions for ", shown on hover and focus and always on a touch pointer, plus right-click, Shift+F10 or the context-menu key on the item, which open the same menu anchored to the row. A chosen descriptor is emitted as the Workbench'sactionevent with the item as its second argument. Escape returns focus to the item, or to the button the menu opened from. The buttons sit in a column beside the listbox, never inside it, since a listbox may own only options. The listbox's arrows, filter and F6 are unchanged, and so is Alt+↑/↓ reordering. An item without actions has no button and keeps the browser's context menu. The browser job (Playwright with axe-core) checks a three-item Workbench: no axe violations, no focusable element inside the listbox, and the Shift+F10, right-click and Escape paths. - Server-side item menus:
actionson the PHPWorkbenchItem(docs/workbench.md › Item menus).WorkbenchItemtakesactions, a list of the kit'sAction(default empty, no menu), and a WorkbenchViewModel sends them to the page unchanged; the generated type hasactions: Action[]. The MCPWorkbenchReadToolstill gives each item exactly{ id, label, type, description }: row-menu actions are UI affordances, and no MCP tool exposes action descriptors.composer test:workbench-item-paritypins the PHP keys to the TSWorkbenchItem, the generated type, the read tool's keys and the Inertia round trip of Move up, Duplicate and Delete. #navigator-actionson the Workbench (docs/workbench.md › Navigator actions). The app's own controls, such as "+ New slide", render first in the navigator, before its filter and list, at every width (columns, lg, phone tabs and the overlay sheet). So the visual and tab order match withoutorder-first. With the slot empty, the navigator renders as before.- session-ui route groups and
kit.home_name.kit.routes.session-ui.<group> => falseswitches off one group of session-ui's routes, and their names, at a time:invitations,welcome,passkey-offerordemo-login. Every group is on by default, and the kit's own redirects and props for a group that is off are guarded. An unknown key or a non-boolean value stops the boot with an error that names the groups.kit.home_name(KIT_HOME_NAME, defaulthome) names the kit's home route, so an app that already ownshomekeeps it; the kit's breadcrumbs follow the new name. Checked bycomposer test:route-groups.
Fixed
laravel/mcp1.0.0 and 1.0.1 broke every route load. The kit callsMcp::oauthRoutes(registration: false), whoseregistrationparameter arrived in laravel/mcp 1.0.2; on an older install every request failed with "Unknown named parameter $registration". The requirement is nowlaravel/mcp: ^1.0.2, the first release with every laravel/mcp API the kit uses.- The Workbench reorder kept a null version token after Inertia v3 returned a string body, so the next write got a 409. Inertia v3's HTTP client resolves
dataas the raw response text, andsendOrderreadresponse.data.versionfrom it, so after every reorder the token was lost and the next stale-safe write (another move, an add, a duplicate, a delete) was refused asstale_write. Bodies now go through core's newresponseJson, which returns an objectdataas it is and parses a string as JSON, and throws on an empty or non-JSON body instead of handing back null (allowEmptyreturns null where a caller expects no body);errorJsonreads a refused request's body, as null when it isn't JSON, since the status has already decided. The same string-body bug lost the stored layout and version on a 409 from the Workbench layout endpoint (sendLayout), and the creation options in the passkey offer (registerPasskey); both read through it now. A 2xx reorder answer that isn't JSON is a failed save, so the old order comes back.tests/js/response-json.test.mjscovers the helper,workbench.test.mjsandonboarding.test.mjsstring bodies, and a browser check makes two moves and a Duplicate against a stand-in server that refuses a stale or null version. - The kit's Inertia props survive
Inertia::flushShared().kitUi,kitToasts,shell,environment,kitPasswordandpasskeyOfferwere shared once at provider boot, so anything that flushed Inertia's shared props afterwards dropped them: pest-plugin-browser flushes before each request, so a host's browser tests rendered blank pages, and an Octane-style reset or a host's own flush would too. Each provider now hands its closures toKitShares, which shares them at boot and again at the start of every web request (now first inweb, see below). They stay lazy closures, one producer each. The host still wins: the kit puts back only the keys Inertia's factory doesn't already hold, and the host's ownHandleInertiaRequestsmerges over them, so a key the host shares (in its middleware, or on the factory before the request) is never overwritten. The kit still never flushes.test:inertia-sharesandtest:request-hooks(through the real HTTP kernel) flush between requests and check every kit prop, the host's ownshellandenvironment, and two people in turn. Kit/Layout/Page.vueis a valid component again. A merge in v0.6.10 left<<<<<<<,=======and>>>>>>>lines around the page header, and they shipped in v0.6.10, v0.6.11 and v0.6.12. The header is now the status and actions version (HeaderStatus,status,actions) with thefont-displayheading face, as both sides intended. A new CI check,composer test:conflict-markers, fails on any conflict marker (indented ones included) underresources/orsrc/.- The kit's request start now runs first in
web.PrepareKitRequestwas appended, so it ran last: a host's own middleware earlier inweb(a tenant resolver) read memberships before the request memo began, and repeated their queries; after anInertia::flushShared()a host share wrapping a kit prop (Inertia::getShared('shell')) captured null, so the page fell back to the guest layout; and a route-model-binding 404 never reached it, so the 404 page had no kit props. session-ui now prepends a newStartKitRequesttoweband to the middleware priority list. It begins the request's memos (the registry's newstartRequestUsinghooks: the request memo and access's permission, capability and entitlement memos) and puts the kit's props back (KitShares::restore()), neither of which needs the session or the signed-in person. The session-dependent hook (the local brand switch) stays late inPrepareKitRequest, which also restores once more. The host still wins: restore only fills keys Inertia's factory doesn't hold, and the host's ownHandleInertiaRequestsmerges over them. Each memo is scoped to its request, so nothing carries over.composer test:request-order(new, in CI) runs a host through the real HTTP kernel and checks that the host's second membership lookup adds no query, that the wrapped shell and a binding 404 after a flush both have the kit's shell, that a hostshellshare still wins, and that a membership removed between two requests isn't answered from the first's memo. - Kit form controls put your attributes on the control itself, not a wrapper.
aria-describedby,aria-invalid,required,aria-labelanddata-*passed to InputCheckbox landed on its wrapper<div>; on Select they went nowhere (Reka's SelectRoot renders no element); FileInput dropped them; RadioCardGroup and InputNumber replaced or ignored youraria-describedby; InputRichText put them on its frame; and InputSelect dropped a passedrequired. Each now forwards them to its focusable control (the checkbox button, the select trigger, the file input, the radiogroup, the editable textbox, the combobox input), joins youraria-describedbywith its own ids, de-duplicated, and setsaria-requiredwhere a nativerequireddoesn't apply.classand a style's custom properties stay on the wrapper. InputCheckbox, FileInput, RadioCardGroup and InputRichText takeinvalid(aria-invalid), and InputCheckbox takesdescription, shown under the label as<id>-descriptionand joined to itsaria-describedby. - A toast no longer covers a filling Workbench's actions bar. The bar is a StickyBar at the viewport's foot by layout, not by scroll, so it never counted as pinned and never registered with useStickyBars: a bottom toast sat on top of its buttons (Save) and took their clicks.
StickyBartakespinnedfor a bar the layout holds at the foot, and the Workbench passes it while it fills.useStickyBars().track()also re-measures when itsenabledstate changes, not only on resize, so a bar that becomes pinned by scrolling registers at once. FloatingToolbar (and BulkBar on it) already registered while floating. - Mail text is escaped exactly once.
FluentMailMessageHTML-encoded user text and the markdown render encoded it again, so an apostrophe showed as a literal'("Ada's talk") in the HTML part, and the text part showed the entities and markdown backslashes (\*x\*).escape()now only backslash-escapes markdown punctuation and leaves HTML encoding to the render, and each block carries its text as given for the text part.<script>and<b>still show as inert text in every builder (headline, paragraph, facts, table, panel, callout, note, bullets, quote, section, button labels) in both layouts. A host that passedFluentMailMessage::escape()output tomarkdown()gets the same HTML; its text part shows that block's backslashes, as before.
Changed
layout.pagereads hand-rolled pages in apps on the kit shell again. Theui.kit-shellrule is back beside the PageHeader file-name signal: the rule reads an app that adopted the kit shell, the file name a legacy app. Where the rule can't run (no Pest beside standards), plainstandards adoptionreports it asskippedand the other signals decide the row;--ciand--compliancestill fail closed. Kit's fixture check now asserts each case's expected skips, and fails on one it doesn't list.- PaneTabs' pressed pane is neutral. The Workbench's phone tabs (
Kit/Layout/PaneTabs.vue) took the primary button for the pressed pane, so it read as a second primary action. They now take SegmentedToggle's look: a bordered raised segment in a muted track. They stay a single-choice toggle group (role="group",aria-pressed). - The adoption inventory is calibrated against legacy apps. A
*Panel,*Historyor*Chipfile, a drag-and-drop library on its own, and a<table>with no header row no longer count as hand-rolled; cards count only outside public pages.layout.pagereads hand-rolled from an app's own page header without the kit shell,shell.workspacefrom a starter-kitAppLayout, and page actions in an app layout's#headerslot count.overlay.modalreplaces@inertiaui/modal-vue. Each change has an adoption fixture, and the fixtures run on tey/standards 0.11.47. - The adoption inventory no longer reads the app's shadcn primitives or content renderers as hand-rolled. Every
resources/js/**file-name signal skipsresources/js/components/ui/**, the primitives the kit builds on, soui/dropdown-menu/*no longer marksaction.menuhand-rolled. The*Card.vuesignal forlayout.blocksandhas-content-panelsalso skipsbody/,content/,render/andrenderers/folders, where a card draws content rather than framing a page. New fixturescards-content-rendererandui-primitives-onlycover both. Aui/button/Button.vuestill reads hand-rolled through standards' generic-name scan ofreplaces.components, which has no path filter yet.
Fixed
- The adoption schema accepts what tey/standards 0.11.50 accepts.
resources/adoption/surfaces.schema.jsongains theclass-referencesignal kind (classes, optionalpaths,minandscope), file-nameexceptand template-tagin(vue,bladeor both; defaultvue), so packages that copy the schema can use them. A new CI check,composer test:adoption-schema, compares the schema with the installed standards' signal kinds, fields, required fields, groups and analyses, and fails on any drift. The kit's tey/standards dev dependency is now ^0.11.50.
From teylabs.com's adoption of the Workspace Layout: - A host without workspaces gets its navigation. PageShell sent an empty navigation and account menu on a core and shell host. It now lists the pages registered with
Registry::navigation()at root-relative URLs, current by the sameactiverules, each shown while the viewer passes its ability as a Laravel Gate ability. An undefined or denied ability hides the link (fail closed).user-menulinks, such as a Settings item, join the account menu. - The Layout no longer crashes on a focus shell, and FocusShell ships. For a
kind: 'focus'shell (welcome steps, no workspace yet), the Workspace Layout renders the new FocusShell: the product, an optional "Step n of m", and the account cluster with Switch account and Sign out. It's also exported from@kit/shell, and takes:toaster. - The home crumb is recognised by path. gretel's breadcrumb URLs are absolute (
https://host/@slug), so the root crumb never matched and showed as text. It now shows as the house icon. - The
extraslot says where it's rendered. It's rendered in both the account dropdown and the phone drawer, now with aplaceslot prop (menuordrawer), so an app can render a DropdownMenuItem in one and a plain link in the other. A newdrawer-extraslot replaces it in the drawer. - The current page stands out in PrimaryNav. It now gets a tinted pill and a 3px marker beside full-strength text, not only an underline in the brand marker's colour.
- Destructive menu items and error text meet WCAG AA. They used
--destructiveas text, 3.76:1 on the white popover in light mode and 4.00:1 on a focused item in dark mode (AA is 4.5:1). The new--destructive-texttoken is destructive text on a surface;--destructive-foregroundkeeps its meaning, text on a destructive background, and solid destructive backgrounds are unchanged. Its defaults arehsl(0 72% 42%)light (6.48:1 on background, card and popover, 5.94:1 on accent and muted) andhsl(0 91% 71%)dark (7.20:1 on background, card and popover, 5.51:1 on accent, 5.29:1 on muted).tests/js/destructive-text.test.mjscomputes those ratios fromkit.cssand refuses a rawtext-destructivein any stub, and the browser job's open Workbench row menu now runs axe'scolor-contrastwith the destructive Delete item showing (browser/README.md says why contrast runs use a non-modal surface). FormStatusmounted before a visit now shows the message the visit brings back. It kept ahiddenflag set by the router'sstartand by a watcher onform.processing, and cleared only byfinish, so a form whoseprocessingchanged without a router visit left it hidden for good. Visibility is now derived from state: it shows when there is a message and no visit is in flight (the router betweenstartandfinish, orform.processing), so after each visit it shows exactly when the new page carries a message. The browser job mounts it before the visit and drives visits through the Inertia stub in v3's order (start, the page swap,flash,success,finish).- The Workbench's phone navigator is a flex column, as on wider screens. It was only
min-h-0, so a host'sorder-firstchild sat under the actions bar on phones.
What's Changed
- Workbench navigator item menus, with the buttons outside the listbox by @jaspertey in #370
- Calibrate the adoption inventory against legacy apps by @jaspertey in #366
- Shell gaps from a Workspace Layout adoption: navigation without workspaces, FocusShell, home crumb, extra's place, nav active state (2732) by @jaspertey in #367
- Destructive text meets AA on surfaces: a --destructive-text token by @jaspertey in #372
- Send row-menu actions on server-built Workbench items by @jaspertey in #376
- FormStatus: visibility from state, and a flashKey for Inertia v3's page.flash by @jaspertey in #379
- Restore the kit's Inertia shares after a flush, host shares still winning by @jaspertey in #381
- Workbench #navigator-actions, first in the navigator; the phone navigator is a flex column (2697) by @jaspertey in #385
- Keep the Workbench reorder's version token: parse Inertia v3's string bodies by @jaspertey in #392
- Checkbox menu items and a #navigator-item slot for the Workbench by @jaspertey in #390
- Sync the adoption schema with tey/standards 0.11.49 and guard against drift by @jaspertey in #382
- Put ui.kit-shell back beside PageHeader and assert its skip by @jaspertey in #383
- Skip shadcn primitives and content renderers in adoption signals by @jaspertey in #402
- Resolve the conflict markers shipped in Kit/Layout/Page.vue (v0.6.10–v0.6.12); CI guard by @jaspertey in #411
- Run the kit's request start first in web by @jaspertey in #413
- Forward caller attributes to the focusable control in kit form controls by @jaspertey in #409
- StickyBar pinned by layout registers with useStickyBars (toasts clear a filling Workbench's actions bar) by @jaspertey in #397
- Restore the browser suite cut to one test, and fail CI below a test floor by @jaspertey in #416
- FluentMailMessage: escape user text exactly once (no ' in mail) by @jaspertey in #399
- session-ui route groups (kit.routes.session-ui.) and kit.home_name by @jaspertey in #417
- Workbench navigator thumbnails and a filmstrip layout by @jaspertey in #407
- Require laravel/mcp ^1.0.2 (oauthRoutes registration parameter) by @jaspertey in #403
- A neutral SegmentedToggle, and PaneTabs without a primary fill by @jaspertey in #396
- Release v0.6.13 by @jaspertey in #424
Full Changelog: v0.6.12...v0.6.13