Skip to content

v0.7.0

Choose a tag to compare

@jaspertey jaspertey released this 30 Oct 07:01
99861a6

Upgrading

  • Run kit:update plan|apply for mcp (re-pinned sha256:29d14952…750e → sha256:e35e2879…7ae0): Mcp/Authorize/Page.vue takes the "Also allow" grant checkboxes below. Nothing to do for an app with no explicit tool: the fieldset renders only when there is a grant to offer.
  • Run kit:update plan|apply for shell (re-pinned sha256:6b2a9dac…5077 → sha256:93780271…035d): the manifest gains the vite_global_types host step below; no stub changes.
  • The kit's runtime floors are now tey/mod ^0.11.6, tey/standards ^0.11.51, spatie/image ^3.9 and laravel/mcp ^1.0.2 (see Fixed). An app on an older version of any of them gets the new one on composer update; nothing else to do.
  • Run kit:update plan|apply for mcp (re-pinned sha256:7e095959…f01b → sha256:29d14952…750e): Mcp/Authorize/Page.vue shows the redirect host and the unverified-app note below, and the migration 0001_01_02_000603_create_kit_mcp_registrations_table is new. Then php artisan migrate.
  • The mcp feature now serves its own /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource documents (root and path-suffixed) and no longer calls Mcp::oauthRoutes(). A host that re-declared those routes over laravel/mcp's (to advertise its scopes) can drop them: scopes_supported is the kit's token scopes, which include the host's. A host that re-declared POST /oauth/register over laravel/mcp's controller can drop that too and set kit.mcp.registration to true; its existing clients keep working. token_endpoint_auth_methods_supported is now ["none"] only.
  • A host's unregistered-redirect_uri test that expected Passport's 401 invalid_client JSON for a browser request now gets the kit's 400 page; JSON requests keep the 401. The laravel/mcp requirement is ^1.0.2.
  • A client that registered http://localhost/... (or http://127.0.0.1/..., http://[::1]/...) now matches that redirect on any port at authorize and token time (RFC 8252 §7.3), as Claude Code's per-session port needs. Only the port is free: the scheme, host, path and query still match exactly, and nothing changes for https redirects.
  • New migration for mcp, 0001_01_02_000603_create_kit_mcp_registrations_table (the kit_mcp_registrations table: which OAuth clients came from POST /oauth/register). Run kit:update plan|apply for mcp, then php artisan migrate. Until it is migrated, registration answers 500 server_error and kit:mcp:clients refuses. A client registered before (laravel/mcp's registration, or the kit's before this table) is not in it: php artisan kit:mcp:clients --adopt=<client id> records one by hand, so that --revoke and --prune-unused can touch it; the kit never guesses.
  • Run kit:update plan|apply for core (re-pinned sha256:ea1d2e42…3b8c → sha256:652e0756…7ac3): Kit/Form/Amount.vue is new, and Kit/Form/InputNumber.vue, Kit/Form/number.ts, Kit/contract.ts and Kit/index.ts change for percent, symbols and the shared kitNumber defaults.
  • Money inputs no longer default to CAD. Set KIT_CURRENCY=CAD (config kit.currency) to keep the old symbol, or pass currency per input.
  • Run kit:update plan|apply for core (re-pinned sha256:652e0756…7ac3 → sha256:ca87cd7a…7242): Kit/Form/AddressFieldset.vue, Kit/Form/AddressText.vue, Kit/Form/RegionSelect.vue and Kit/Form/address.ts are new, and Kit/contract.ts and Kit/index.ts export them.
  • Run kit:update plan|apply for core (re-pinned sha256:ca87cd7a…7242 → sha256:bc488ba6…e150): Kit/Form/InputTags.vue and Kit/Form/tags.ts are new, and Kit/contract.ts and Kit/index.ts export them. InputTags puts a caller's aria-describedby, aria-invalid and required on its input, as InputSelect does.
  • Run kit:update plan|apply for core (re-pinned sha256:bc488ba6…e150 → sha256:a3439714…0e66): this adds Kit/Data/DateTime.vue, dateTime.ts and useTimezone.ts.
  • Run kit:update plan|apply for core (re-pinned sha256:a3439714…0e66 → sha256:962f4983…6ecb): this adds the pickers (Kit/Form/InputDate.vue, InputDateRange.vue, InputMonth.vue, InputYear.vue, InputTime.vue, InputTimeRange.vue, PeriodPicker.vue and dateInput.ts), and css/kit.css gains their phone-sheet rule. core now declares @internationalized/date (^3.5.0, already installed with reka-ui) in its npm requirements.
  • Run kit:update plan|apply for core (re-pinned sha256:962f4983…6ecb → sha256:a04c162c…2dd6): Kit/Layout/stickyBars.ts takes the fix below. A bar registered with register(px) keeps working: px now means its inset from the viewport's bottom, which for a bar flush with the bottom is its height.
  • Run kit:update plan|apply for core (re-pinned sha256:a04c162c…2dd6 → sha256:81fceb81…95fd) and notes (sha256:d373d438…f1b2 → sha256:1a0455e7…551b): css/kit.css adds the --success-text, --warning-text and --info-text tokens, Kit/Form/FormActions.vue shows "Saved" in text-success-text, Kit/Feedback/alertVariants.ts gives a pale Alert the theme's foreground for its text and a *-text token for its icon, and the Notes page's colours.ts gives a coloured note the theme's foreground. No kit stub colours text with a raw palette colour any more.
  • A host with its own theme (not importing kit.css) must define --success-text, --warning-text and --info-text for light and dark; the kit's AA values are a starting point: hsl(142 76% 26%), hsl(26 90% 33%) and hsl(201 90% 32%) light, hsl(142 69% 58%), hsl(43 96% 56%) and hsl(199 89% 64%) 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.
  • session-ui's confirm-password Cancel is now resolved by the bindable Tey\Kit\Features\SessionUi\Contracts\ConfirmPasswordCancelUrl (default ConfirmPasswordCancel). Nothing to do unless you want your own: bind it. No stub changes, no re-pin.
  • Run kit:update plan|apply for core (re-pinned sha256:81fceb81…95fd → sha256:4a94fc7c…105e): Kit/Appearance/useAppearance.ts takes the storage fix below. No other stub changes.
  • Run kit:update plan|apply for core (re-pinned sha256:4a94fc7c…105e → sha256:6537c1e1…3fa7): Kit/Workbench/Workbench.vue takes the phone-tabs fix below.
  • Run kit:update plan|apply for session-ui (re-pinned sha256:71617f5d…3108 → sha256:592e21c4…7353): Auth/ResetPassword/Page.vue gains a FormStatus (below).

Added

  • kit:status and kit:add flag an undefined connection ability. kit.mcp.connection_ability defaults to settings.read, which the settings feature declares; a host without it (or with its own value) had every MCP connection refused as permission_denied. With the mcp feature, the host requirements now carry a kit.mcp.connection_ability config step: met when a feature declares the ability through the Registry or the Gate defines it, else attention naming the key, the effect and the three fixes. test:status covers the default without settings, an undefined value, a Gate-defined one and a Registry-declared one.
  • Explicit grants for MCP tools (docs/mcp-consent.md › Explicit grants). #[Capability(…, scope: 'x', explicit: true, explains: '…')] marks a tool that a blanket acts-as-me connection never covers: it is listed and callable only under a connection whose consent named its scope, and a call anyway is refused scope_required (403), never reported as unknown. The kit's consent page offers each such scope the person may hold as an unticked "Also allow" checkbox showing the scope's label and explains; only the tick grants (the OAuth scope parameter never does), an existing connection never gains one (skipped consent and refresh keep its scopes), and the approval's audit entry records the grants. scope must be a registered token scope and explains is required; registration refuses otherwise. On the AI assistants page each grant has its own Remove action (DELETE …/connections/{id}/grants/{scope}, audited workspace.connection.grant_removed), leaving the connection and its token in place. Registry::explicitScopes(), AuthorizeViewModel::grants(), WorkspaceConnection::explicit() and removeGrant() are new. composer test:mcp-explicit-grants (34 checks).
  • kit:status and kit:add check that the Vite config gives @vitejs/plugin-vue the kit's types (the shell feature's vite_global_types host step; README › Types). A page typed with defineProps<Tey.Kit.…>() is resolved at build time from script.globalTypeFiles, so a production build fails without vendor/tey/kit/types/kit.d.ts there; a tsconfig include alone serves the editor and vue-tsc. The Vite config (vite.config.ts, .js or .mjs) is read as text, never run: the step is met when an array literal under globalTypeFiles names the kit's types (a plain path or a path.resolve(...) of it), unmet with the exact snippet vue({ script: { globalTypeFiles: ['vendor/tey/kit/types/kit.d.ts'] } }) when the key is missing or the list lacks it, and "can't verify" (unmet, never passed) when there is no Vite config, it can't be read, or globalTypeFiles is not an array literal. Attention, not blocking, like the other host steps. For a config the kit cannot read, kit.host.vite_global_types = 'manual' (KIT_HOST_VITE_GLOBAL_TYPES=manual) records that a person verified it; the step then reads "verified manually". composer test:status covers each case.
  • kit.mcp.server.page_size (docs/mcp-tools.md › The server's name and instructions). How many tools, resources or prompts one MCP list answer carries, 50 by default (laravel/mcp's maximum; larger values are clamped, a non-number falls back to 50). laravel/mcp's default of 15 hid every tool past the fifteenth from assistants that never follow nextCursor. composer test:mcp-server-config lists thirty tools in one page by default, pages at a host's 10, and clamps 500 to 50.
  • MCP resources from the host (docs/mcp-tools.md › Resources). Registry::mcpResources([...]) takes laravel/mcp Resource classes or descriptors (uri, name, read, and optionally mimeType, description, title, capability), and the workspace server lists and serves them under the same admission as tools: a resource with #[Capability] (using GatedByCapability) or a descriptor's capability is absent while that capability is off or out of plan, and a read of it anyway is refused with the reason (the kit's resources/read method), not as unknown; a resource without one (docs) is always listed. A read runs inside the connection's workspace, and a descriptor's closure receives it. Refused at registration: a non-Resource class, a gated class without the trait, an undeclared capability, a descriptor missing its uri, name or read, a relative uri, a uri registered twice, or any of it without the mcp feature. composer test:mcp-resources covers the list, reads, the gate off and on, an unknown URI and each refusal.
  • MCP client registration and discovery, the kit's own (docs/mcp-registration.md). Opt-in OAuth 2.0 Dynamic Client Registration (RFC 7591) at POST /oauth/register, kit.mcp.registration (KIT_MCP_REGISTRATION, off by default: the route does not exist), for public clients only: an assistant such as Claude registers itself with no secret issued and no management credentials. Redirect URIs are checked at registration: https with a host, http to a loopback host on any port (Claude Code's per-session port, RFC 8252), or a private-use scheme the host lists in kit.mcp.redirect_schemes (empty by default); never a wildcard, fragment, userinfo, plain http elsewhere, or javascript, data, blob, file. A per-IP rate limit (kit.mcp.registration_per_minute, 10) and an optional kit.mcp.registration_policy (a RegistrationPolicy class refusing with a reason, 403 access_denied). The client is the Passport row laravel/mcp's registration stored, so a client registered before keeps working, with no column added. The discovery documents (RFC 8414 and RFC 9728, root and /mcp) are now the kit's, under laravel/mcp's route names, advertising only what the kit implements: code, authorization_code and refresh_token, S256, public clients, the kit's token scopes, and registration_endpoint only when registration is on; Mcp::oauthRoutes() is no longer called. The 401 on /mcp adds scope="mcp:use" to laravel/mcp's challenge. Passport's client bridge matches a loopback localhost redirect without its port, as league already does for 127.0.0.1. kit:mcp:clients lists registered clients, --revoke=<id> revokes one with its tokens, --prune-unused=<days> revokes those with no token in that time (--dry-run to look first). composer test:mcp-registration covers each RFC requirement, the refusals, the limit, the policy, the metadata shape, a pre-existing client and registration off.
  • The consent page names where the assistant sends you. It shows the redirect host Passport verified for the request, and for a self-registered client (dynamic client registration, or an owner-less public client shaped like one) adds "Unverified app": the name is the client's own claim, so a person can tell a lookalike from the app they expect. AuthorizeViewModel gains redirect_host and registered; the Mcp/Authorize/Page.vue stub shows them.
  • PKCE S256 required at authorize. Every authorization request must carry a code_challenge (43 to 128 unreserved characters) with code_challenge_method=S256; a missing challenge or the plain method is refused with 400 before league sees it, never redirected. Claude and every MCP client send S256 already.
  • The OAuth error page, brandable. When an authorization request cannot go back to its app (unknown client, unregistered redirect_uri, bad PKCE), a browser now gets a readable 400 page instead of Passport's bare invalid_client JSON; JSON callers and errors sent back to a verified redirect URI are unchanged. The kit's kit::oauth-error view by default; a host sets kit.mcp.oauth_error_view to its own view, or OAuthErrorPage::using(fn (Request, string $error, ?string $description): Response) for its own page (an Inertia error page, say). A host that subclassed Passport's authorization controller for this can drop that.
  • Money and percent: <InputNumber kind="percent"> and <Amount> (docs/money-percent.md). Percentages are canonical in percentage points ("12.5" is 12.5 %). Money and percent inputs show the locale's symbol outside the editable text and name the unit in the label; negative="parentheses" gives the accounting form. <Amount> displays the same canonical strings in tabular figures. Locale and currency default to the new shared kitNumber (config kit.currency, the app locale), then the browser.
  • <AddressFieldset>, <RegionSelect> and <AddressText> (docs/address.md). A postal address as one named group, submitted as address[line1] … address[country], with autocomplete tokens, labels that follow the country (Province or territory and Postal code, State and ZIP code), Canadian postal codes written A1A 1A1 on blur, and non-blocking hints for unlikely CA and US codes. Regions are the Canadian provinces and territories and the US states and DC, named in English or French; other countries take free text. <AddressText> shows an address in an <address> element, with a map link only when the app passes its own mapUrl. Labels come from the new address.* keys in the kit's ui lang file.
  • <InputTags>, free-text tags as chips (docs/input-tags.md). Built on InputSelect (multiple and creatable), so the combobox, chips and announcements are the same. A separator (, or ;) or Enter adds the typed tag, and Enter takes a suggestion only after the arrow keys move to it. A paste with separators or line breaks adds each piece. Duplicates (ignoring case in the locale), tags over maxLength, tags not matching pattern and tags past max are refused with the reason under the field, never trimmed. Submitted as name[]. Reasons come from the new tags.* keys in the kit's ui lang file.
  • DateTime, dates and times for the viewer (docs/date-time.md). It renders a <time> with the machine value and the full date in its title, in the formats relative, local, date, time, ymd and datetime. A calendar date (YYYY-MM-DD) is never converted between zones, so it never shifts across midnight or DST. An instant (RFC 3339 with its offset) shows in the viewer's timezone, and one without an offset is shown as given, never guessed. live re-renders relative times on one shared minute ticker. formatDateTime(), fullDateTime() and useTimezone() are exported too. It uses Intl only, with no date library.
  • session-ui shares kitTimezone, the viewer's zone (the tz cookie, then the person's saved zone, then the workspace's, then UTC), through KitShares, so it survives a flush. DateTime, and the pickers to come, read it.
  • The adoption inventory knows data.date-display (a new shows-dates fact). Hand-rolled timeago.js and moment components, and TimeAgo/DateRelative-style files, count against it.
  • Date and time pickers (docs/date-pickers.md): InputDate, InputDateRange, InputMonth, InputYear, InputTime and InputTimeRange, on reka-ui. Each submits plain ISO strings (2026-11-01, 2026-11, 2026, 09:30; a range as name[start] and name[end]). A date, month, year or time is a wall value, never converted between zones; kitTimezone only decides today. The calendars use the APG date picker keys and the month and year grids the APG grid keys; on phones they open as a bottom sheet, and in a locked LockedForm they stay shut. A half-filled or reversed range blocks the native submit. toInstant() and fromInstant() join a date and time into an RFC 3339 instant with the viewer zone's offset (DST gaps move forward, repeated hours take the earlier moment).
  • Tey\Kit\Validation\IsoDate and IsoRange, the server rules for those values: IsoDate::date(), month(), year(), time() and instant() (an offset required) with inclusive bounds, and IsoRange::dates() and times() for {start, end}.
  • Adoption surfaces form.date-pickers and form.time-input, with the facts takes-dates and takes-times.
  • The adoption inventory reads Blade, Livewire and Filament apps (tey/standards 0.11.51 or later). Every template-tag signal reads Blade views as well as Vue ("in": ["vue", "blade"]). Filament resources under app/Filament/** count as hand-rolled through class references: TextInput, Select and Textarea for form.inputs, FileUpload for form.files, Table and TextColumn for data.table, DeleteAction for action.confirm and Notification for feedback.toast, mirrored into the has-forms, has-file-uploads and has-index-pages facts. A new has-filament-destructive-actions fact lets action.confirm apply to a Filament app with no delete route. Blade views count for shell.workspace (the conventional app layout files), layout.page (<x-app-layout>, <x-layouts.app>), action.button (<button>, <x-button> and the Breeze button components), feedback.toast (toast, flash and action-message views) and appearance.theme (theme-toggle, dark-mode and appearance views). platform.timezone gains nothing: no signal kind expresses hand-rolled timezone handling in PHP or Blade without guessing.

Changed

  • Money inputs no longer default to CAD. Without currency or kit.currency, <InputNumber kind="money"> shows no symbol (two decimals, the same canonical minor units) and warns in development. Set KIT_CURRENCY=CAD to keep the old symbol.

Fixed

  • A workspace created in the same request is admitted. IsKitWorkspace read admitted as null until the model was read back, so a workspace made and used in one request was refused admission_required. The trait now mirrors the column's default (true in the kit's migration) on an unsaved or just-created model whose table has the column, so it answers the way its row will; an explicit admitted => false stays false, and admission stays distinct from creation (a host whose adopted column defaults to false sets admitted to false in its model's $attributes). test:workspace-guard covers create-then-check.
  • Leaving a tenant no longer wipes Pennant's array store. TenantBoundary and the FlushFeatureCacheTask switch task flushed Pennant's cache on every switch; under the array store (the kit's default, and tests) that cache is the store itself, so a flag deactivated earlier in the request came back on, and a test that deactivated a capability then called /mcp saw it on. Both now flush only for a store with a backend (TenantBoundary::flushes(): the default store's driver is not array), where the cache can go stale across tenants. test:capability-guard and test:tenant-tasks cover both stores.
  • A narrow connection no longer sees tools it cannot call. GatedByCapability listed tools by capability and role ability only, so a connection whose scopes covered settings.read alone still saw the write tools and got connection_denied on the call. Presence now also requires that the calling connection covers the ability the tool is admitted under, the same WorkspaceConnection::covers() the call path applies, so tools/list exactly matches what is callable; the refusal codes for a call made anyway are unchanged (connection_denied, or scope_required for an explicit tool). Covered by test:mcp-explicit-grants.
  • A long footer or inspector scrolls inside its phone tab. Below lg the Workbench's footer and inspector tabs rendered their slot straight into the tab's row, with no scroll container, so a long speaker note ran on behind the pinned actions bar and its end could not be reached. Both now sit in a PaneScroll, as their panes do at lg and up. The browser suite checks each at 390×844: the tab scrolls, and after scrolling to the end the last line is fully visible above the bar.
  • The reset-password page shows a form status like its siblings. Auth/ResetPassword had no FormStatus, where Auth/ForgotPassword, Auth/Login and Auth/VerifyEmail do. It now has one, first inside the form as on ForgotPassword, showing the status page prop when a host app supplies one.
  • Dependency floors are tested. CI's tests job gains a lowest entry (PHP 8.4, composer update --prefer-lowest --prefer-stable) running every check against the oldest versions composer.json allows, so a requirement whose floor predates an API the kit uses fails in CI rather than on a host. Its first run raises tey/mod to ^0.11.6 (0.11.0 to 0.11.5 lack the rollback fix test:adopt needs) alongside laravel/mcp: ^1.0.2, and adds spatie/typescript-transformer: ^3.3 to require-dev (bin/kit-types.php uses withoutManifest(), new in 3.3.0, which laravel-typescript-transformer ^3.3 alone does not guarantee), symfony/event-dispatcher-contracts: ^3.5 and symfony/polyfill-php83: ^1.32 (older ones emit PHP 8.4 implicit-nullable deprecations at boot, which the checks that require a silent boot read as failures), and spatie/image: ^3.9 in require (laravel-medialibrary ^11.13 allows 3.3, whose WebP fit does not keep the declared width test:media-conversions expects; 3.9.0 is the first that does).
  • FormActions' "Saved" meets WCAG AA. It was text-green-600, about 3.3:1 on a white card (AA is 4.5:1), so axe's color-contrast failed on every FormCard just after a save. The new --success-text token, with --warning-text and --info-text beside it, is status text on a surface, as --destructive-text is: hsl(142 76% 26%) light (5.80:1 on background, card and popover, 5.32:1 on accent and muted) and hsl(142 69% 58%) dark (11.36:1, 8.68:1 on accent, 8.35:1 on muted); warning is 6.04:1 and 11.70:1, info 6.06:1 and 9.71:1, and at least 5.32:1 light and 7.13:1 dark on accent and muted. A pale Alert's icon takes these tokens (and --destructive-text for danger) instead of raw palette colours, and its text, like a coloured note's, takes the theme's foreground. tests/js/semantic-text.test.mjs computes the ratios from kit.css and refuses a raw palette text colour (text-green-600, dark:text-sky-300) in any kit resource, and the browser job's ?form-saved page runs axe's color-contrast over a saved FormCard and the pale Alerts, light and dark.
  • A bottom toast clears a pinned bar that the layout holds above the viewport's bottom. The sticky-bar registry recorded each bar's height, and the Toaster lifted bottom toasts by that much. A filling Workbench's actions bar sits wherever the layout puts it, which under a shell shorter than its 8rem allowance is above the bottom edge, so a bottom-right toast landed on its right-hand buttons (Present, Save). Each bar now registers its inset (the distance from the viewport's bottom to its top), re-measured on scroll and window resize, and toasts clear the largest one. A browser test asserts that no bar button is under a toast, checking each button's left, centre and right, at 1440×900 with bottom-right toasts and at a phone width.
  • browser/ and typecheck/ ignore a symlinked node_modules (the pattern no longer ends in /, which matched directories only), so a contributor's linked install can't be committed. The resource checks refuse a tracked symlink.
  • session-ui's login page now shows Concierge's one-click local sign-in. The login view passed no localLogin, so the button never appeared; it now carries LocalLogin::offer(), which stays the only gate (null outside local, and without Concierge).
  • Confirm-password Cancel falls through instead of jumping home. A refused cancel query (off the app, protocol-relative, backslash, javascript:, credentials, encoded) now falls to the referer, then to the intended URL one level up when a real non-guest route answers there, then kit.home.
  • kit:status flags a missing kit.home route. With session-ui and no workspace-ui, sign-in and Concierge's no-workspace redirect land on kit.home; with no route answering GET there it now reports attention instead of letting sign-in 404.
  • Blocked browser storage no longer leaves the page blank. useAppearance read and wrote localStorage unguarded (in initializeTheme, on mount and in updateAppearance), so where storage throws on access (site data blocked, some private modes) the app never mounted. Every read and write is now guarded: a failed or unrecognised read falls back to system, and a failed write is ignored (the appearance cookie still carries the choice). The other core stubs that use storage (ComputedValueScope.vue, SplitPane.vue/splitState.ts, DataTable.vue, Workbench.vue/workbenchLayout.ts) were already guarded. The browser suite mounts the app with localStorage and sessionStorage throwing.

What's Changed

  • Add percent inputs and Amount for money and percent display by @jaspertey in #374
  • Add AddressFieldset, RegionSelect and AddressText by @jaspertey in #377
  • Add InputTags for free-text tags as chips by @jaspertey in #384
  • DateTime: dates and times for the viewer; session-ui shares kitTimezone (2737 item 2) by @jaspertey in #387
  • Pickers: InputDate, InputDateRange, InputMonth, InputYear, InputTime, InputTimeRange by @jaspertey in #393
  • Sticky bars register their inset, so bottom toasts clear a bar held above the viewport's bottom by @jaspertey in #428
  • Offer local sign-in, resolve confirm-password Cancel through a contract and flag a missing kit.home route by @jaspertey in #429
  • MCP dynamic client registration (RFC 7591), kit-owned discovery metadata, PKCE S256, brandable OAuth error page by @jaspertey in #406
  • Host-registered MCP resources on the workspace server, gated like tools by @jaspertey in #410
  • CI: test the lowest allowed dependency versions; tey/mod ^0.11.6 by @jaspertey in #415
  • kit.mcp.server.page_size: one tools/list page holds every tool by default by @jaspertey in #426
  • kit:status and kit:add check plugin-vue's script.globalTypeFiles for the kit's types by @jaspertey in #421
  • Explicit grants for MCP tools (opt-in scopes a blanket consent never covers); narrow connections list only callable tools by @jaspertey in #431
  • MCP switch-over fixes: just-created workspace admission, Pennant array store kept across tenant switches, kit:status flags an undefined connection ability by @jaspertey in #433
  • Read Blade, Livewire and Filament apps in the adoption inventory by @jaspertey in #435
  • Semantic status text tokens: FormActions' Saved meets AA by @jaspertey in #441
  • Guard useAppearance's localStorage access (blank page when storage is blocked) by @jaspertey in #444
  • Scroll the Workbench's phone footer and inspector tabs; FormStatus on ResetPassword by @jaspertey in #446
  • Release v0.7.0 by @jaspertey in #452

Full Changelog: v0.6.13...v0.7.0