diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 88f2429..0aa5f50 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -33,6 +33,9 @@ jobs: - name: 🏗️ Set up workspace uses: ./.github/actions/setup-workspace + - name: 🔨 Build packages + run: pnpm build + - name: ✅ Validate packages run: pnpm check:package diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index a9cc036..e79ca2f 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -137,7 +137,9 @@ reinjection. The notification controller is headless. The optional Shadow DOM renderer supplies the default light/dark theme, typed CSS-variable customization and runtime theme updates without rebuilding controls. Hosts choose branding, placement and approval callbacks. A host using Devframe -notifications uses its message API instead of injecting the default renderer. +notifications uses its message API instead of injecting the default renderer. The panel host owns +each shared Devframe notification. Viewing tabs own matching local command registrations, keeping +approval in the clicked tab while avoiding duplicate messages across connected tabs. ### Automation providers @@ -377,3 +379,18 @@ not parse the message. Unit tests cover protocol behavior. Repository integration tests cover authenticated transports, browser clients, extension helpers, package consumers, and example compositions. Embedding hosts remain responsible for end-to-end validation of their approval policy and platform adapter. + +## Native page WebMCP + +The optional Chrome adapter owns native WebMCP discovery, document-bound references and invocation +completion. The kernel authorizes the transport-neutral `Bridge.listWebMcpTools` and +`Bridge.invokeWebMcpTools` operations, reserving native `WebMCP.*` commands and events. Broker lease +demand owns domain activation; the extension serializes activation and catalogue refresh against +the current main document. There is no injected JavaScript fallback or iframe aggregation. + +MCP exposes two stable tools with principal-owned target references. Page registrations remain +result data. Listing requires inspect access; invocation requires an exclusive interact lease even +when the page advertises a read-only annotation. Installation discovery policy never grants or +revokes invocation authority. Cancellation and lost results after dispatch report unknown outcomes +without replay. Large results retain the existing lease-owned artifact mechanism; semantic artifact +reads stay bound to the tool session. See [WebMCP](docs/webmcp.md). diff --git a/docs/benchmarks/2026-09-09-aligned.md b/docs/benchmarks/2026-09-09-aligned.md index 72299e0..47c22be 100644 --- a/docs/benchmarks/2026-09-09-aligned.md +++ b/docs/benchmarks/2026-09-09-aligned.md @@ -105,7 +105,8 @@ Estimated payload tokens below are serialized tool argument and response charact The exact hub-ui 0.9.10 patch from Devframe PR #375 is exercised in the installed embedded and standalone browser bundles. Both hosts pass deletion, full reconciliation, changed descriptions, unchanged publications, manual dismissal and intentional resurfacing. Eight page-script tests cover direct Accept/default Review, stable message registrations, count changes, serialized updates and creation/expiry/disposal races. The MCP package's 67 unit regressions also pass. -Workspace patches do not propagate to consumers of published CDB packages. See [patch provenance and removal conditions](../../patches/README.md). +The benchmark used the Devframe versions pinned by that revision. CDB no longer carries the +temporary Devframe patches after the fixes shipped upstream. ## Follow-up lifecycle matrix diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..e2d4f7d --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,44 @@ +# Defining adapter configuration + +Each configurable adapter exports a `define…` helper beside its factory. Helpers return the original +object, preserve callback types and literal values, and check configuration without opening +connections, installing listeners, reading stores, or starting timers. Factories still accept plain +options and apply the same checks when the helper is omitted. + +```ts +import { createChromeProvider, defineProvider } from '@dvcol/cdb-extension/chrome'; + +const definition = defineProvider({ + maximumLevel: 'interact', + connect: connectProvider, + authorizeApproval, +}); + +const provider = createChromeProvider(definition); +provider.start(); +``` + +`connectProvider` and `authorizeApproval` remain host callbacks. Defining configuration never calls +them. Mutable runtime dependencies, regular expressions, and callback closures are retained by +reference. Helpers do not serialize options or allocate default resources. Environment-dependent +checks, such as availability of Chrome, belong to runtime construction or connection. + +| Entry | Helpers | +| --- | --- | +| Core | `defineTargetBroker`, `defineEmbeddedBridge`, `defineAgentConnection`, `defineClientConnection`, `defineGrantRequests`, `defineLogicalSession` | +| Broker | `defineBroker` | +| Extension `/chrome` | `defineProvider` | +| Extension | `definePublisher`, `defineSelectedTab`, `defineTabLifecycle`, `defineTabScope`, `defineApproval`, `defineApprovalSender`, `defineRecovery`, `defineBootstrap`, `defineOfferRelay`, `definePairingStore`, `definePageRequest` | +| Extension `/notifications` | `defineNotifications`, `defineNotificationRenderer` | +| WebSocket `/browser` | `defineAgentWebSocket`, `defineClientWebSocket`, `defineBrowserClient` | +| WebSocket `/node` | `defineWebSocketBridge`, `defineStandaloneWebSocket`, `defineClientWebSocket`, `defineNodeClient`, `defineStandaloneHost`, `defineArtifactEndpoint`, `defineArtifactReader`, `defineArtifactStore` | +| Birpc `/node` | `defineBridge` | +| Devframe | `defineService`, `definePanel` | +| Devframe `/connection` | `defineConnection` | +| Devframe `/client` | `defineClientSession`, `defineProvider` | +| Devframe `/panel`, `/page-script` | `definePanelMount`, `definePage` respectively | +| MCP | `defineTools`, `defineHttp`, `defineStdio` | +| Playwright automation | `defineProvider` | + +Import aliases distinguish similarly named helpers when composing several adapters. Functions that +only wrap a live connection or platform object do not need a configuration helper. diff --git a/docs/webmcp-validation.md b/docs/webmcp-validation.md new file mode 100644 index 0000000..082126f --- /dev/null +++ b/docs/webmcp-validation.md @@ -0,0 +1,68 @@ +# WebMCP validation handoff + +Work for [issue 70](https://github.com/dvcol/chrome-debugger-bridge/issues/70) is staged in two local +CDB branches: `dvcol/define-adapters` from `main`, then `dvcol/webmcp`. The adapter configuration layer +is commit `76b9b99`. Downstream branches are both named `dvcol/cdb-webmcp`: app-frontends starts at +`88518587946` on `dvcol/cdb`, and QA Helper starts at `b86e151c` on `dvcol/cdb`. + +## Current boundary + +Another session owns the live downstream TNR. Do not start this change's live servers, Chromium +fixtures, extension reloads, downstream installs/builds, or full TNR until the user explicitly gives +the go-ahead. PR submission is also held because CDB PR CI starts full verification. + +CDB checks performed before that boundary use the in-memory Chrome command port, broker, client +facade and MCP session. They cover main-document selection, empty/unsupported discovery, filtering, +callback failure and mutation isolation, generation/document references, exclusive invocation, +principal isolation, artifacts, cancellation and late responses. Package-scoped TypeScript checks, +lint and builds validate the public entrypoints. These are not live Chromium proof. + +The dedicated app-frontends and QA Helper worktrees contain source, test and documentation changes +only. Their dependency manifests and installed packages have not been changed. They still reference +CDB 0.2.0, which does not export these new helpers. Consequently those downstream branches are not +ready for CI or release until dependency alignment is completed. No downstream tests have been run. + +## Recorded pre-live checks + +On 2026-09-14, 164 focused CDB tests passed: 78 core catalogue/authority/target-directory/scaffold +checks and 86 extension/MCP controller, publisher, session and semantic-tool checks. The latter +include 23 dedicated WebMCP cases. Type checks passed for core, extension, MCP, broker, Devframe, +WebSocket and Birpc, plus a scoped compilation of the new tests and native fixture. Core, +extension and MCP packages built successfully, changed TypeScript sources passed lint, and the +CDP catalogue regeneration check passed. Downstream checks and live fixtures remain unrun. + +## Resume after the user's go-ahead + +1. Re-read the branches and working-tree state. Confirm the other TNR has released its servers, + browser profile, extension and package outputs before touching them. +2. Build and pack the CDB stack in dependency order. For local proof, install those tarballs only in + the dedicated downstream worktrees and keep overrides local. Do not commit absolute filesystem + paths or invent a published version. Before downstream PR submission, update manifests, both + app-frontends catalogue definitions and lockfiles to the actual CDB release containing the stack. +3. Run the scoped DevKit/DevTools and QA Helper type, lint, format, quality and affected unit-test + scripts required by their guides. Validate the QA Helper discovery exclusion for DevKit's exported + `WEBMCP_TOOL_PREFIX`. Preserve installation identity, pairing database and storage keys. +4. Run the authored CDB native fixture: + + ```sh + pnpm exec vitest run --project extension-e2e tests/e2e/native-webmcp.test.ts + ``` + + It uses an isolated Chromium profile, enables experimental web platform features, and registers + a real tool on a trustworthy loopback page. It exercises the public MCP/client/broker/extension + path, confirms iframe exclusion and a visible native side effect, then checks stale references + and an empty catalogue after reload. It has only been statically checked so far. A missing native + `document.modelContext` fails the test explicitly; record the Chrome version and flags. +5. Through DevKit MCP in the intended Chrome environment, request inspect access and list page tools. + Verify QA Helper and application tools appear while `dev_square_` tools are hidden. Invocation at + inspect must fail. At interact, invoke a known page tool and verify its visible effect. Check + direct invocation of a hidden name, cancellation, navigation, authority renewal, another + principal's isolation, and an artifact-sized result. Never replay an unknown invocation outcome. +6. Run the full CDB validation gates and downstream TNR inventories, including the public Devframe + path, local shell and staging. When full local CDB gates are explicitly authorized, constrain + Turbo and Vitest concurrency to one and use `NODE_OPTIONS=--max-old-space-size=4096`. Keep latency + measurements separate from builds or other browser runs. Do not use DevKit's default registry + port for temporary fixtures. +7. Review the diffs, submit the configuration/WebMCP PR stack, then submit downstream PRs against + their intended CDB branches. Include actual test evidence and remaining browser limitations; + do not label the live behavior verified until the preceding steps pass. diff --git a/docs/webmcp.md b/docs/webmcp.md new file mode 100644 index 0000000..4fac9ad --- /dev/null +++ b/docs/webmcp.md @@ -0,0 +1,124 @@ +# Native page WebMCP + +CDB exposes the approved page's native WebMCP registrations through two stable tools: + +| MCP tool | Client method | Minimum access | Lease | +| --- | --- | --- | --- | +| `browser.list_webmcp_tools` | `listWebMcpTools` | `inspect` | Shared read | +| `browser.invoke_webmcp_tools` | `invokeWebMcpTools` | `interact` | Exclusive control | + +Both target the main document. There is no frame argument, frame aggregation, injected registration +hook, or JavaScript fallback. An empty native catalogue returns `tools: []`; a browser without the +native CDP WebMCP domain returns `FEATURE_UNSUPPORTED`. + +## Discovery policy + +The extension installation chooses discovery policy through `defineProvider` or `definePublisher`. +Configuration is fixed for the adapter lifetime; callbacks can read changing application state and +are evaluated on each listing. + +```ts +import { createChromeProvider, defineProvider } from '@dvcol/cdb-extension/chrome'; + +const provider = createChromeProvider(defineProvider({ + connect: connectProvider, + maximumLevel: 'interact', + authorizeApproval, + webMcp: { + discovery: { + enabled: ({ url }) => new URL(url).hostname.endsWith('.example.com'), + include: [/^catalog_/u, 'search'], + exclude: [ + 'catalog_delete', + /^internal_/u, + ({ tool }) => tool.annotations?.untrustedContent === true, + ], + }, + }, +})); +``` + +`connectProvider` and `authorizeApproval` are host callbacks. Schema conditions use ordinary +JavaScript or a host's existing validator; CDB adds no schema-matching dependency. For example, +`exclude: [({ tool }) => forbiddenSchema.safeParse(tool.inputSchema).success]` can use a host-owned +schema validator. + +Discovery defaults to enabled with every tool included. If `include` is present, a tool must match at +least one entry; an empty include array includes nothing. Any matching exclusion wins. Matchers may +be exact names, regular expressions, or synchronous/asynchronous callbacks. Callbacks receive the +page URL, target ID, target generation, and a tool descriptor with cloned schema and annotation data. +Stateful regular expressions do not mutate the host's `lastIndex`. A thrown or rejected callback +fails the entire listing with `WEBMCP_DISCOVERY_FAILED`; CDB never returns a partly filtered list. + +These controls affect discovery only. An approved caller with `interact` access may invoke a hidden +tool by name, including when discovery is disabled. Use the provider's command authorization policy +to impose additional execution restrictions. Tool annotations are page-provided hints and never +reduce the required access level or lease mode. + +## Calls and references + +```json +{ "targetRef": "t1" } +``` + +Pass this to `browser.list_webmcp_tools`. Its result contains `documentRef`, `enabled`, and `tools`. +Each tool has `name`, `description`, `toolRef`, and any native `inputSchema` and `annotations`. +Native stack traces, backend node IDs, and frame IDs are not returned. + +Invoke one tool through `browser.invoke_webmcp_tools`: + +```json +{ "targetRef": "t1", "toolName": "search", "input": { "query": "coffee" } } +``` + +Use either `toolName` or a returned `toolRef`, never both. Names resolve in the current main document +without a preceding list. References are opaque and bound to the target generation and document; +navigation, authority renewal, or removal makes them stale. List again after `WEBMCP_TOOL_STALE`. +References confer no authority. Every call still passes the current principal's grant and lease checks. + +The client facade offers the same operations through an explicit existing lease: + +```ts +const result = await client.listWebMcpTools({ + targetId, targetGeneration, leaseId, operationId: crypto.randomUUID(), +}); +const invoked = await client.invokeWebMcpTools({ + targetId, targetGeneration, leaseId: exclusiveLeaseId, + operationId: crypto.randomUUID(), toolName: 'search', input: { query: 'coffee' }, +}); +``` + +Acquire those leases for `Bridge.listWebMcpTools` and `Bridge.invokeWebMcpTools`, respectively. +Semantic MCP sessions acquire temporary leases and keep target generations internal. Raw native +`WebMCP.*` commands and events are reserved, including at `unsafe` access, so they cannot bypass +provider discovery and invocation handling. + +## Results and interruption + +Successful invocation returns `{ "status": "completed", "output": ... }`. Output may be any JSON +value and remains untrusted page content. Page tools are returned as data; they are not registered +as additional MCP tools. + +Results above the broker's inline limit use its existing artifact store. Semantic WebMCP responses +contain `artifact`, `leaseId`, `expiresAt`, and `targetRef`. Read bounded base64 ranges with +`browser.read_artifact` and release with `browser.release_artifact`, supplying that target reference, +artifact ID, and lease ID. Releasing the artifact also releases its temporary lease. Session disposal, +client replacement, or target revocation releases retained leases. The lease can expire earlier than +the artifact descriptor; use the response's lease expiry. Another session cannot read the artifact. + +Cancellation after dispatch attempts `WebMCP.cancelInvocation` and returns +`WEBMCP_OUTCOME_UNKNOWN`. Navigation, revocation, or transport loss may also leave an unknown outcome. +No automatic replay is performed: page effects may have happened before the result was lost. +A native tool error returns `CDP_COMMAND_FAILED`; a native cancellation returns `REQUEST_CANCELLED`. +The extension's maximum result size remains an outer bound for catalogues and invocation output. + +## Native protocol basis + +The implementation uses [`WebMCP.enable`, `invokeTool`, and `toolResponded`](https://chromedevtools.github.io/devtools-protocol/tot/WebMCP/). +Chrome's [DevTools session handler](https://github.com/chromium/chromium/blob/main/chrome/browser/devtools/chrome_devtools_session.cc) +permits the WebMCP domain for extension debugger clients. The +[Blink inspector agent](https://github.com/chromium/chromium/blob/main/third_party/blink/renderer/core/inspector/inspector_web_mcp_agent.cc) +seeds existing tools during enable. The empty catalogue has no initial event; CDB uses completion of +the enable command rather than waiting for a tools-added event. Main-document selection avoids +claiming complete same-process iframe discovery. Live verification is still required against the +Chrome build used by an embedding application. diff --git a/packages/automation-playwright/src/index.ts b/packages/automation-playwright/src/index.ts index 0e7476c..5428935 100644 --- a/packages/automation-playwright/src/index.ts +++ b/packages/automation-playwright/src/index.ts @@ -794,5 +794,12 @@ class PlaywrightAutomationProvider implements AutomationProvider { export function createPlaywrightAutomationProvider( options: PlaywrightAutomationProviderOptions = {}, ): AutomationProvider { + defineProvider(options); return new PlaywrightAutomationProvider(options); } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineProvider(definition: Definition): Definition { + validateTimeoutMilliseconds({ ...defaultPlaywrightAutomationTimingPolicy, ...definition.timing }.actionTimeoutMilliseconds, 'actionTimeoutMilliseconds'); + return definition; +} diff --git a/packages/birpc/src/node.ts b/packages/birpc/src/node.ts index 12fbef3..6c150ed 100644 --- a/packages/birpc/src/node.ts +++ b/packages/birpc/src/node.ts @@ -24,7 +24,7 @@ import type { BirpcSubscriptionDescriptor, } from './client.js'; -import { connectAgentTargetBroker, connectClientTargetBroker, createTargetBroker } from '@dvcol/cdb'; +import { connectAgentTargetBroker, connectClientTargetBroker, createTargetBroker, defineTargetBroker, validateTimeoutMilliseconds } from '@dvcol/cdb'; import { mountAuthenticatedWebSocketBridge } from '@dvcol/cdb-websocket/node'; import { createBirpc } from 'birpc'; @@ -88,6 +88,7 @@ export function mountBirpcChromeDebuggerBridge< AgentPrincipal extends AuthenticatedPrincipal, ClientPrincipal extends AuthenticatedPrincipal, >(options: MountBirpcChromeDebuggerBridgeOptions): MountedBirpcChromeDebuggerBridge { + defineBridge(options); const broker = options.broker ?? createTargetBroker(options); const ownsBroker = options.broker === undefined; const subscriptions = new Map(); @@ -255,3 +256,14 @@ export function mountBirpcChromeDebuggerBridge< }, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineBridge = MountBirpcChromeDebuggerBridgeOptions>(definition: Definition & MountBirpcChromeDebuggerBridgeOptions): Definition { + if (definition.broker === undefined) defineTargetBroker(definition); + for (const path of [definition.agentPath, definition.clientPath]) { + if (!path.startsWith('/') || path.includes('?') || path.includes('#')) throw new Error('WebSocket paths must be absolute paths without query parameters or fragments'); + } + if (definition.agentPath === definition.clientPath) throw new Error('Agent and client WebSocket paths must be distinct'); + for (const [name, value] of Object.entries(definition.webSocketTiming ?? {})) validateTimeoutMilliseconds(value, name); + return definition; +} diff --git a/packages/broker/src/runtime.ts b/packages/broker/src/runtime.ts index 9d1b18d..e21b8a6 100644 --- a/packages/broker/src/runtime.ts +++ b/packages/broker/src/runtime.ts @@ -150,6 +150,18 @@ export async function createBroker(configuration: BrokerDefinition = {}): Promis return { principalId: provider.registration.instanceId, connectionGeneration: provider.generation }; } + function requireAvailableProvider(): void { + const connected = [...providers.values()]; + if (connected.some(provider => provider.state === 'ready')) return; + if ( + connected.some(provider => provider.state === 'connecting' || provider.state === 'recovering') + || [...registrations.values()].some(registration => !providers.has(registration.id)) + ) { + throw new BrokerError('PROVIDER_RECOVERING', 'A browser provider is connecting or recovering.', true); + } + throw new BrokerError('PROVIDER_UNAVAILABLE', 'No browser provider is available.'); + } + function assertProviderIdentity(registration: ProviderRegistration): void { const existing = [...providers.values()].find(provider => provider.registration.id === registration.id || provider.registration.instanceId === registration.instanceId); if (existing !== undefined && (existing.registration.id !== registration.id || existing.registration.instanceId !== registration.instanceId)) @@ -426,6 +438,22 @@ export async function createBroker(configuration: BrokerDefinition = {}): Promis } } + async function reconcileProviderState(provider: Provider, values: readonly ProviderTarget[]): Promise { + if (providers.get(provider.registration.id) !== provider || (provider.state !== 'connecting' && provider.state !== 'ready')) + throw new BrokerError('PROVIDER_RECOVERING', 'The provider connection is unavailable.', true); + const checked = checkedTargets(provider, values); + for (const target of checked) targetMetadata.set(target.id, target); + provider.state = 'ready'; + for (const session of sessions.values()) session.refreshTargets(); + for (const state of grantCoordinator.inspect()) { + if (state.request.state !== 'granted' || state.provider?.principalId !== provider.registration.instanceId) continue; + const selected = checked.filter(target => state.bindings.some(binding => binding.targetId === target.id)); + await reconcileScope(provider.peer, state.request.id, selected); + } + changed(); + return snapshot(); + } + function accessResult(grant: BrokerGrant, session: BrokerSession): BrowserAccessResult { const published = targetBroker.listTargets(session.authority).find(target => target.id === grant.targetId); const targetRef = published === undefined ? undefined : session.tools.projectTarget(published)?.targetRef; @@ -481,6 +509,7 @@ export async function createBroker(configuration: BrokerDefinition = {}): Promis return accessResult(candidates[0], session); } if (input.newTarget !== true && targetIds.size > 1) throw new BrokerError('TARGET_AMBIGUOUS', 'Choose a targetRef or request a new target.', true); + requireAvailableProvider(); if (grantCoordinator.inspect().some(state => state.request.logicalSessionId === session.logicalSessionId && state.request.state !== 'granted')) throw new BrokerError('REQUEST_RATE_LIMITED', 'This browser session already has a pending request.', true); const previous = lastRequests.get(session.principal.id); @@ -570,6 +599,14 @@ export async function createBroker(configuration: BrokerDefinition = {}): Promis disconnect: connectAgentTargetBroker(connection, targetBroker, { authority, connectionGeneration: authority.connectionGeneration, + onTargetsReconciled(targets) { + void reconcileProviderState(provider, targets).catch(() => { + if (providers.get(registration.id) !== provider) return; + provider.disconnect(); + connection.close?.(3001, 'Provider reconciliation failed'); + recover(provider); + }); + }, revokeTargetsOnDisconnect: false, connectionLimits: { maximumArtifactBytes: 16 * 1_024 * 1_024, maximumInlineResultBytes: 64 * 1_024, maximumMessageBytes: definition.maximumProviderMessageBytes ?? 64 * 1_024 * 1_024 }, }), @@ -599,18 +636,8 @@ export async function createBroker(configuration: BrokerDefinition = {}): Promis }, async reconcileProvider(peer: BrokerPeer, values: readonly ProviderTarget[]) { const provider = [...providers.values()].find(candidate => candidate.peer.id === peer.id); - if (provider === undefined || (provider.state !== 'connecting' && provider.state !== 'ready')) throw new BrokerError('PROVIDER_RECOVERING', 'The provider connection is unavailable.', true); - const checked = checkedTargets(provider, values); - for (const target of checked) targetMetadata.set(target.id, target); - provider.state = 'ready'; - for (const session of sessions.values()) session.refreshTargets(); - for (const state of grantCoordinator.inspect()) { - if (state.request.state !== 'granted' || state.provider?.principalId !== provider.registration.instanceId) continue; - const selected = checked.filter(target => state.bindings.some(binding => binding.targetId === target.id)); - await reconcileScope(peer, state.request.id, selected); - } - changed(); - return snapshot(); + if (provider === undefined) throw new BrokerError('PROVIDER_RECOVERING', 'The provider connection is unavailable.', true); + return reconcileProviderState(provider, values); }, claimRequest(peer: BrokerPeer, requestId: string) { const provider = currentProvider(peer); @@ -663,12 +690,40 @@ export async function createBroker(configuration: BrokerDefinition = {}): Promis }, async disconnectProvider(providerId: string, forgetPairing = false) { const provider = providers.get(providerId); - if (provider === undefined) return false; + const registration = provider?.registration ?? [...registrations.values()].find(candidate => candidate.id === providerId); + if (registration === undefined || (provider === undefined && !forgetPairing)) return false; + if (provider === undefined) { + for (const [peerId, candidate] of registrations) { + if (candidate.id !== registration.id && candidate.instanceId !== registration.instanceId) continue; + registrations.delete(peerId); + authentication.forget(peerId); + } + let credential = identityStore.findByAgentId(registration.instanceId); + while (credential !== undefined) { + await identityStore.remove(credential.credentialId); + credential = identityStore.findByAgentId(registration.instanceId); + } + changed(); + return true; + } provider.state = 'disconnected'; if (provider.recoveryTimer !== undefined) clearTimeout(provider.recoveryTimer); + if (forgetPairing) { + providers.delete(providerId); + for (const [peerId, registration] of registrations) { + if (registration.id !== provider.registration.id && registration.instanceId !== provider.registration.instanceId) continue; + registrations.delete(peerId); + authentication.forget(peerId); + } + } provider.disconnect(); provider.connection.close?.(4001, 'Provider disconnected'); + const targetIds = targetBroker.listTargets() + .filter(target => targetBroker.getTargetAgentPrincipalId(target.id) === provider.registration.instanceId) + .map(target => target.id); targetBroker.revokeAgentTargets(providerAuthority(provider), 'explicit'); + for (const targetId of targetIds) targetMetadata.delete(targetId); + changed(); await cancelProviderRequests(provider); if (forgetPairing) { let credential = identityStore.findByAgentId(provider.registration.instanceId); @@ -677,7 +732,6 @@ export async function createBroker(configuration: BrokerDefinition = {}): Promis credential = identityStore.findByAgentId(provider.registration.instanceId); } } - changed(); return true; }, async disconnectPeer(peerId: string) { diff --git a/packages/broker/src/session-client.ts b/packages/broker/src/session-client.ts index cf43fee..d5e3416 100644 --- a/packages/broker/src/session-client.ts +++ b/packages/broker/src/session-client.ts @@ -5,6 +5,7 @@ import type { ClientAuthority } from '@dvcol/cdb/broker'; import type { BrokerDefinition } from './config.js'; import type { BrokerPeer, BrokerPrincipal } from './contract.js'; +import { createWebMcpClient } from '@dvcol/cdb'; import { createCdbToolSession } from '@dvcol/cdb-mcp'; import { allowsNavigation } from './config.js'; @@ -94,6 +95,7 @@ export function createBrokerSession(context: SessionContext, principal: BrokerPr } const client: McpChromeDebuggerBridgeClient = { + ...createWebMcpClient({ executeCommand: async request => authorized(async authority => context.targetBroker.executeCommand(request, authority)) }), acquireLease: async request => authorized((authority) => { const lease = context.targetBroker.acquireLease(request, authority); context.changed(); diff --git a/packages/broker/test/runtime.test.ts b/packages/broker/test/runtime.test.ts index b551c03..067d10c 100644 --- a/packages/broker/test/runtime.test.ts +++ b/packages/broker/test/runtime.test.ts @@ -3,7 +3,7 @@ import type { AgentToBrokerMessage, BrokerToAgentMessage, PublishedTarget } from import type { BrokerRuntime } from '../src/runtime.js'; import { createAgentAuthenticationProof, decodeBase64UrlBytes, generateRandomBase64Url, importAgentCredential } from '@dvcol/cdb/authentication'; -import { afterEach, describe, expect, it } from 'vitest'; +import { afterEach, describe, expect, it, vi } from 'vitest'; import { createMemoryBrokerIdentityStore } from '../src/identity-store.js'; import { createBroker } from '../src/runtime.js'; @@ -53,7 +53,7 @@ async function fixture(accessRequestTimeoutMilliseconds = 60_000) { const target: PublishedTarget = { id: crypto.randomUUID(), generation: 1, scopeId: crypto.randomUUID(), availability: 'available', capabilities: { level: 'debug' }, type: 'page', url: 'https://example.test/start', title: 'Public fixture' }; send({ kind: 'notification', method: 'targets.publish', protocolVersion: 1, parameters: { target } }); await broker.reconcileProvider(peer, [target]); - return { broker, identityStore, peer, received, registration, send, target }; + return { broker, closeProvider: closed.resolve, credential, identityStore, key, peer, received, registration, send, target }; } async function approve(setup: Awaited>, principalId: string, navigation: 'same-origin' | 'follow-tab' = 'same-origin') { @@ -91,14 +91,182 @@ describe('composed browser broker', () => { }); it('forgets every retained credential for a provider installation', async () => { - expect.assertions(4); + expect.assertions(11); const setup = await fixture(); const credential = setup.identityStore.load('credential')!; await setup.identityStore.activate({ ...credential, credentialId: 'another-existing-credential' }); + await approve(setup, 'approved-agent'); expect(await setup.broker.disconnectProvider(setup.registration.id, true)).toBe(true); expect(setup.identityStore.load('credential')).toBeUndefined(); expect(setup.identityStore.load('another-existing-credential')).toBeUndefined(); - expect(setup.broker.createPairingOffer(setup.peer).brokerId).toBe(setup.broker.brokerId); + expect(setup.broker.snapshot().providers).toEqual([]); + expect(setup.broker.snapshot().targets).toEqual([]); + expect(setup.broker.snapshot().requests).toEqual([]); + expect(setup.broker.snapshot().grants).toEqual([]); + setup.closeProvider(); + await setup.broker.disconnectPeer(setup.peer.id); + await Promise.resolve(); + expect(setup.broker.snapshot().providers).toEqual([]); + expect(() => setup.broker.beginProviderAuthentication(setup.peer, { + credentialId: 'credential', + clientNonce: generateRandomBase64Url(32), + })).toThrow('Register the provider before authenticating'); + setup.broker.registerProvider(setup.peer, setup.registration); + expect(() => setup.broker.beginProviderAuthentication(setup.peer, { + credentialId: 'credential', + clientNonce: generateRandomBase64Url(32), + })).toThrow(); + }); + + it('keeps a normal disconnect visible and paired for explicit reconnection', async () => { + expect.assertions(4); + const setup = await fixture(); + expect(await setup.broker.disconnectProvider(setup.registration.id)).toBe(true); + expect(setup.broker.snapshot().providers).toMatchObject([{ id: setup.registration.id, paired: true, state: 'disconnected' }]); + expect(setup.identityStore.load('credential')).toBeDefined(); + expect(setup.broker.beginProviderAuthentication(setup.peer, { + credentialId: 'credential', + clientNonce: generateRandomBase64Url(32), + }).agentId).toBe(setup.registration.instanceId); + }); + + it.each([ + ['without a registered provider', 'unavailable'], + ['with a disconnected provider', 'disconnected'], + ['with an offline provider', 'offline'], + ['while a provider is connecting', 'connecting'], + ] as const)('rejects access %s without creating or rate-limiting a request', async (_label, providerState) => { + expect.assertions(4); + const identityStore = createMemoryBrokerIdentityStore(); + const broker = await createBroker({ identityStore, timing: { requestRateLimitMilliseconds: 60_000, ...(providerState === 'offline' ? { providerRecoveryMilliseconds: 0 } : {}) } }); + brokers.push(broker); + const peer = { id: 'preflight-provider' }; + const registration = { id: 'preflight-provider', instanceId: crypto.randomUUID(), name: 'Preflight provider', version: '1.0.0', maximumLevel: 'debug' as const }; + if (providerState !== 'unavailable') { + broker.registerProvider(peer, registration); + if (providerState === 'disconnected' || providerState === 'offline') { + const invitation = broker.createPairingOffer(peer); + const credential = generateRandomBase64Url(32); + const transcript = broker.beginProviderAuthentication(peer, { credentialId: 'preflight-credential', clientNonce: generateRandomBase64Url(32), pairing: { code: invitation.code, credential } }); + const key = await importAgentCredential(decodeBase64UrlBytes(credential)); + const closed = Promise.withResolvers(); + await broker.authenticateProvider(peer, await createAgentAuthenticationProof(key, transcript), { + closed: closed.promise, + close: () => closed.resolve(), + onMessage: () => () => {}, + send: async () => {}, + }); + if (providerState === 'disconnected') await broker.disconnectProvider(registration.id); + else { + await broker.disconnectPeer(peer.id); + await vi.waitUntil(() => broker.snapshot().providers[0]?.state === 'offline'); + } + } + } + const expected = providerState === 'connecting' + ? { code: 'PROVIDER_RECOVERING', retryable: true } + : { code: 'PROVIDER_UNAVAILABLE', retryable: false }; + await expect(broker.invoke({ id: 'preflight-agent' }, 'browser.request_access', { level: 'interact' })).rejects.toMatchObject(expected); + expect(broker.snapshot().requests).toEqual([]); + expect(broker.snapshot().leases).toEqual([]); + expect(broker.snapshot().providers.map(provider => provider.state)).toEqual(providerState === 'unavailable' ? [] : [providerState]); + }); + + it('does not consume the request rate limit when provider preflight fails', async () => { + expect.assertions(2); + const identityStore = createMemoryBrokerIdentityStore(); + const broker = await createBroker({ identityStore, timing: { requestRateLimitMilliseconds: 60_000 } }); + brokers.push(broker); + const agent = { id: 'rate-limit-agent' }; + await expect(broker.invoke(agent, 'browser.request_access', { level: 'interact' })).rejects.toMatchObject({ code: 'PROVIDER_UNAVAILABLE' }); + + const peer = { id: 'rate-limit-provider-peer' }; + const registration = { id: 'rate-limit-provider', instanceId: crypto.randomUUID(), name: 'Rate limit provider', version: '1.0.0', maximumLevel: 'debug' as const }; + broker.registerProvider(peer, registration); + const invitation = broker.createPairingOffer(peer); + const credential = generateRandomBase64Url(32); + const transcript = broker.beginProviderAuthentication(peer, { credentialId: 'rate-limit-credential', clientNonce: generateRandomBase64Url(32), pairing: { code: invitation.code, credential } }); + const key = await importAgentCredential(decodeBase64UrlBytes(credential)); + const listeners = new Set<(message: AgentToBrokerMessage) => void>(); + const closed = Promise.withResolvers(); + const authenticated = await broker.authenticateProvider(peer, await createAgentAuthenticationProof(key, transcript), { + closed: closed.promise, + close: () => closed.resolve(), + onMessage(listener) { + listeners.add(listener); + return () => listeners.delete(listener); + }, + send: async () => {}, + }); + const send = (message: AgentToBrokerMessage): void => { + for (const listener of listeners) listener(message); + }; + send({ kind: 'request', method: 'agent.hello', protocolVersion: 1, requestId: crypto.randomUUID(), parameters: { + connectionGeneration: authenticated.claims.connectionGeneration, + implementation: { instanceId: registration.instanceId, name: registration.name, role: 'agent', version: registration.version }, + protocolVersions: { minimum: 1, maximum: 1 }, + features: [], + heartbeat: { intervalMilliseconds: 15_000, timeoutMilliseconds: 45_000 }, + limits: { maximumArtifactBytes: 16_777_216, maximumInlineResultBytes: 65_536, maximumMessageBytes: 67_108_864 }, + } }); + const target: PublishedTarget = { id: crypto.randomUUID(), generation: 1, scopeId: crypto.randomUUID(), availability: 'available', capabilities: { level: 'debug' }, type: 'page', url: 'https://example.test/rate-limit' }; + send({ kind: 'notification', method: 'targets.reconcile', protocolVersion: 1, parameters: { targets: [target] } }); + const pending = broker.invoke(agent, 'browser.request_access', { level: 'interact' }); + void pending.catch(() => {}); + await expect.poll(() => broker.snapshot().requests).toHaveLength(1); + }); + + it('lets one stable replacement provider reach ready after stale peer callbacks', async () => { + expect.assertions(6); + const setup = await fixture(); + const replacementPeer = { id: 'replacement-provider-peer' }; + setup.broker.registerProvider(replacementPeer, setup.registration); + const transcript = setup.broker.beginProviderAuthentication(replacementPeer, { + credentialId: 'credential', + clientNonce: generateRandomBase64Url(32), + }); + const listeners = new Set<(message: AgentToBrokerMessage) => void>(); + const received: BrokerToAgentMessage[] = []; + const closed = Promise.withResolvers(); + const authenticated = await setup.broker.authenticateProvider( + replacementPeer, + await createAgentAuthenticationProof(setup.key, transcript), + { + closed: closed.promise, + close: () => closed.resolve(), + onMessage(listener) { + listeners.add(listener); + return () => listeners.delete(listener); + }, + async send(message) { + received.push(message); + }, + }, + ); + setup.closeProvider(); + await setup.broker.disconnectPeer(setup.peer.id); + const send = (message: AgentToBrokerMessage): void => { + for (const listener of listeners) listener(message); + }; + const helloRequestId = crypto.randomUUID(); + send({ kind: 'request', method: 'agent.hello', protocolVersion: 1, requestId: helloRequestId, parameters: { + connectionGeneration: authenticated.claims.connectionGeneration, + implementation: { instanceId: setup.registration.instanceId, name: setup.registration.name, role: 'agent', version: setup.registration.version }, + protocolVersions: { minimum: 1, maximum: 1 }, + features: [], + heartbeat: { intervalMilliseconds: 15_000, timeoutMilliseconds: 45_000 }, + limits: { maximumArtifactBytes: 16_777_216, maximumInlineResultBytes: 65_536, maximumMessageBytes: 67_108_864 }, + } }); + const target = { ...setup.target, generation: 2 }; + send({ kind: 'notification', method: 'targets.reconcile', protocolVersion: 1, parameters: { targets: [target] } }); + await Promise.resolve(); + + expect(received).toContainEqual(expect.objectContaining({ kind: 'response', method: 'agent.hello', requestId: helloRequestId })); + expect(setup.broker.snapshot().providers).toMatchObject([{ id: setup.registration.id, state: 'ready', targetCount: 1 }]); + expect(setup.broker.snapshot().targets).toMatchObject([{ id: target.id, generation: 2, state: 'available' }]); + expect(setup.broker.snapshot().providers).toHaveLength(1); + expect(setup.broker.snapshot().providers[0]!.instanceId).toBe(setup.registration.instanceId); + expect(setup.broker.snapshot().providers[0]!.paired).toBe(true); }); it('serializes concurrent membership publications for one approved scope', async () => { diff --git a/packages/core/scripts/cdp-classification-policy.ts b/packages/core/scripts/cdp-classification-policy.ts index 9d57ad1..8f602d8 100644 --- a/packages/core/scripts/cdp-classification-policy.ts +++ b/packages/core/scripts/cdp-classification-policy.ts @@ -30,11 +30,12 @@ export const supportedChromeDebuggerDomains = [ 'Tracing', 'WebAudio', 'WebAuthn', + 'WebMCP', ] as const; export type SupportedChromeDebuggerDomain = typeof supportedChromeDebuggerDomains[number]; -export const kernelOwnedDomains = ['Target'] as const satisfies readonly SupportedChromeDebuggerDomain[]; +export const kernelOwnedDomains = ['Target', 'WebMCP'] as const satisfies readonly SupportedChromeDebuggerDomain[]; type ClientFacingChromeDebuggerDomain = Exclude; @@ -158,7 +159,6 @@ export const commandLevelOverrides = { 'Log.clear': 'interact', 'Log.startViolationsReport': 'debug', 'Log.stopViolationsReport': 'debug', - 'Network.clearAcceptedEncodingsOverride': 'interact', 'Network.clearBrowserCache': 'interact', 'Network.clearBrowserCookies': 'interact', 'Network.configureDurableMessages': 'debug', @@ -172,7 +172,6 @@ export const commandLevelOverrides = { 'Network.loadNetworkResource': 'interact', 'Network.overrideNetworkState': 'interact', 'Network.replayXHR': 'interact', - 'Network.setAcceptedEncodings': 'interact', 'Network.setAttachDebugStack': 'debug', 'Network.setBlockedURLs': 'interact', 'Network.setBypassServiceWorker': 'interact', @@ -239,16 +238,11 @@ export const commandLevelOverrides = { 'Storage.clearCookies': 'interact', 'Storage.clearDataForOrigin': 'interact', 'Storage.clearDataForStorageKey': 'interact', - 'Storage.clearSharedStorageEntries': 'interact', 'Storage.clearTrustTokens': 'interact', - 'Storage.deleteSharedStorageEntry': 'interact', 'Storage.deleteStorageBucket': 'interact', 'Storage.overrideQuotaForOrigin': 'interact', - 'Storage.resetSharedStorageBudget': 'interact', 'Storage.runBounceTrackingMitigations': 'interact', 'Storage.setCookies': 'interact', - 'Storage.setSharedStorageEntry': 'interact', - 'Storage.setSharedStorageTracking': 'debug', 'Storage.setStorageBucketTracking': 'debug', 'Storage.trackCacheStorageForOrigin': 'debug', 'Storage.trackCacheStorageForStorageKey': 'debug', diff --git a/packages/core/src/agent-target-connection.ts b/packages/core/src/agent-target-connection.ts index 0ac5b15..1c08c22 100644 --- a/packages/core/src/agent-target-connection.ts +++ b/packages/core/src/agent-target-connection.ts @@ -4,6 +4,7 @@ import type { TimeoutMilliseconds } from './timing.js'; import packageManifest from '../package.json' with { type: 'json' }; import { scheduleTimeout, validateTimeoutMilliseconds } from './timing.js'; +import { WebMcpError, webMcpMethods } from './webmcp.js'; export interface AgentTargetConnection { readonly closed?: Promise; @@ -25,6 +26,8 @@ export interface ConnectAgentTargetBrokerOptions { readonly role: 'broker'; readonly version: string; }; + /** Runs after an authenticated `targets.reconcile` message updates the target broker. */ + readonly onTargetsReconciled?: (targets: readonly PublishedTarget[]) => void; /** Defaults to true. Recovery-aware hosts can retain targets and revoke them after their own deadline. */ readonly revokeTargetsOnDisconnect?: boolean; readonly timing?: Partial; @@ -57,6 +60,7 @@ export function connectAgentTargetBroker( broker: TargetBroker, options: ConnectAgentTargetBrokerOptions = {}, ): () => void { + defineAgentConnection(options); const connectionGeneration = options.connectionGeneration ?? 1; const timing: AgentConnectionTimingPolicy = { ...defaultAgentConnectionTimingPolicy, ...options.timing }; const handshakeTimeoutMilliseconds = timing.handshakeTimeoutMilliseconds; @@ -93,7 +97,7 @@ export function connectAgentTargetBroker( pendingCommands.set(requestId, { reject, resolve }); }); const cancelCommand = (): void => { - pendingCommands.get(requestId)?.reject(new Error('The debugger command was cancelled.')); + pendingCommands.get(requestId)?.reject(command.method === webMcpMethods.invoke ? new WebMcpError('WEBMCP_OUTCOME_UNKNOWN', 'The invocation was interrupted after forwarding to the provider. Do not replay it.') : new Error('The debugger command was cancelled.')); pendingCommands.delete(requestId); observeDetachedSend(connection.send?.({ kind: 'notification', method: 'cdp.cancel', parameters: { operationId: command.operationId, targetGeneration: command.targetGeneration, targetId: command.targetId }, protocolVersion: 1 })); }; @@ -218,6 +222,7 @@ export function connectAgentTargetBroker( publishedTargets.set(target.id, target); registerExecutor(target); } + options.onTargetsReconciled?.(message.parameters.targets); } else if (message.method === 'targets.revoke') { broker.revokeTarget(message.parameters.targetId, message.parameters.targetGeneration, message.parameters.reason, authority); publishedTargets.delete(message.parameters.targetId); @@ -249,3 +254,13 @@ export function connectAgentTargetBroker( pendingCommands.clear(); }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineAgentConnection(definition: Definition): Definition { + const timing = { ...defaultAgentConnectionTimingPolicy, ...definition.timing }; + validateTimeoutMilliseconds(timing.handshakeTimeoutMilliseconds, 'handshakeTimeoutMilliseconds'); + for (const name of ['heartbeatIntervalMilliseconds', 'heartbeatTimeoutMilliseconds'] as const) { + if (!Number.isSafeInteger(timing[name]) || timing[name] < 1) throw new TypeError(`${name} must be a positive safe integer.`); + } + return definition; +} diff --git a/packages/core/src/broker.ts b/packages/core/src/broker.ts index a2bda93..c5fe529 100644 --- a/packages/core/src/broker.ts +++ b/packages/core/src/broker.ts @@ -177,6 +177,9 @@ export class TargetBrokerError extends Error { | 'CAPABILITY_DENIED' | 'CDP_COMMAND_FAILED' | 'FEATURE_UNSUPPORTED' + | 'WEBMCP_DISCOVERY_FAILED' + | 'WEBMCP_TOOL_STALE' + | 'WEBMCP_OUTCOME_UNKNOWN' | 'LEASE_CONFLICT' | 'LEASE_EXPIRED' | 'LEASE_REQUIRED' @@ -221,6 +224,10 @@ function targetExecutorError(error: unknown): TargetBrokerError | undefined { const code = record.code; if ( code !== 'CDP_COMMAND_FAILED' + && code !== 'FEATURE_UNSUPPORTED' + && code !== 'WEBMCP_DISCOVERY_FAILED' + && code !== 'WEBMCP_TOOL_STALE' + && code !== 'WEBMCP_OUTCOME_UNKNOWN' && code !== 'REQUEST_CANCELLED' && code !== 'SESSION_GENERATION_STALE' && code !== 'SESSION_NOT_FOUND' @@ -355,6 +362,7 @@ export interface TargetBroker { export function createTargetBroker( options: CreateTargetBrokerOptions = {}, ): TargetBroker { + defineTargetBroker(options); const timing: BrokerTimingPolicy = { ...defaultBrokerTimingPolicy, ...options.timing, @@ -757,7 +765,7 @@ export function createTargetBroker( if (setup === undefined) return undefined; return setup.catch((error: unknown) => { domainDemandCountsByKey.delete(demandKey); - throw new TargetBrokerError('CDP_COMMAND_FAILED', { + throw targetExecutorError(error) ?? new TargetBrokerError('CDP_COMMAND_FAILED', { ...(error instanceof Error ? { message: error.message } : {}), }); }); @@ -789,7 +797,7 @@ export function createTargetBroker( method: string, sessionId?: string, ): Promise | undefined { - const domain = method.split('.', 1)[0]; + const domain = method === 'Bridge.listWebMcpTools' || method === 'Bridge.invokeWebMcpTools' ? 'WebMCP' : method.split('.', 1)[0]; if (domain === undefined || !lifecycleManagedDomains.has(domain)) return undefined; const demand = `${domain}.`; @@ -1229,6 +1237,7 @@ export function createTargetBroker( () => abortController.abort(), commandTimeoutMilliseconds, ); + let dispatched = false; try { const domainActivation = ensureLeaseDomainDemand( target, @@ -1237,14 +1246,16 @@ export function createTargetBroker( command.sessionId, ); if (domainActivation !== undefined) await domainActivation; + dispatched = true; const value = await executor.execute( command, abortController.signal, lease, ); if (abortController.signal.aborted) { - recordDiagnostic('REQUEST_CANCELLED'); - throw new TargetBrokerError('REQUEST_CANCELLED'); + const code = dispatched && command.method === 'Bridge.invokeWebMcpTools' ? 'WEBMCP_OUTCOME_UNKNOWN' : 'REQUEST_CANCELLED'; + recordDiagnostic(code); + throw new TargetBrokerError(code); } const externalizedValue = await externalizeJsonResult(value, { expiresAt: artifactLifetimeMilliseconds === null @@ -1269,8 +1280,13 @@ export function createTargetBroker( throw executorError; } if (abortController.signal.aborted) { - recordDiagnostic('REQUEST_CANCELLED'); - throw new TargetBrokerError('REQUEST_CANCELLED'); + const code = dispatched && command.method === 'Bridge.invokeWebMcpTools' ? 'WEBMCP_OUTCOME_UNKNOWN' : 'REQUEST_CANCELLED'; + recordDiagnostic(code); + throw new TargetBrokerError(code); + } + if (dispatched && command.method === 'Bridge.invokeWebMcpTools') { + recordDiagnostic('WEBMCP_OUTCOME_UNKNOWN'); + throw new TargetBrokerError('WEBMCP_OUTCOME_UNKNOWN', { message: 'The provider response was lost after invocation dispatch. Do not replay it.' }); } recordDiagnostic('CDP_COMMAND_FAILED'); throw new TargetBrokerError('CDP_COMMAND_FAILED', { @@ -2139,3 +2155,9 @@ export function createTargetBroker( }, }); } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineTargetBroker(definition: Definition): Definition { + for (const [name, value] of Object.entries({ ...defaultBrokerTimingPolicy, ...definition.timing })) validateTimeoutMilliseconds(value, name); + return definition; +} diff --git a/packages/core/src/cdp-authorization.ts b/packages/core/src/cdp-authorization.ts index a772e53..884460b 100644 --- a/packages/core/src/cdp-authorization.ts +++ b/packages/core/src/cdp-authorization.ts @@ -7,6 +7,8 @@ const capabilityLevelIndex = new Map(cdpCapabilityLevels.map((level, index) => [ const catalogue: Readonly> = cdpCapabilityCatalogue; const bridgeCatalogue: Readonly> = { 'Bridge.childSessionAttached': { kind: 'event', level: 'observe' }, + 'Bridge.listWebMcpTools': { kind: 'command', level: 'inspect' }, + 'Bridge.invokeWebMcpTools': { kind: 'command', level: 'interact' }, 'Bridge.listChildSessions': { kind: 'command', level: 'inspect' }, }; const kernelOwnedNames = new Set(cdpKernelOwnedNames); @@ -20,7 +22,7 @@ export function isKnownCdpEventName(name: string): boolean { /** Resolves the catalogue and exact-name grant without inspecting native CDP payloads. */ export function isCdpNameAllowed(grant: CapabilityGrant, name: string, kind: CdpNameKind): boolean { - if (kernelOwnedNames.has(name)) return false; + if (kernelOwnedNames.has(name) || name.startsWith('WebMCP.')) return false; const entry = catalogue[name] ?? bridgeCatalogue[name]; if (entry !== undefined && entry.kind !== kind) return false; if (grant.allow?.includes(name) ?? false) return true; diff --git a/packages/core/src/cdp-catalogue.generated.ts b/packages/core/src/cdp-catalogue.generated.ts index 69e9556..f2ca46d 100644 --- a/packages/core/src/cdp-catalogue.generated.ts +++ b/packages/core/src/cdp-catalogue.generated.ts @@ -13,7 +13,7 @@ export const cdpCatalogueMetadata = { absentSupportedDomains: [ 'Database', ], - devtoolsProtocolVersion: '0.0.1672245', + devtoolsProtocolVersion: '0.0.1698617', protocolVersion: '1.3', supportedDomains: [ 'Accessibility', @@ -43,6 +43,7 @@ export const cdpCatalogueMetadata = { 'Tracing', 'WebAudio', 'WebAuthn', + 'WebMCP', ], } as const; @@ -109,6 +110,14 @@ export const cdpKernelOwnedNames = [ 'WebAudio.enable', 'WebAuthn.disable', 'WebAuthn.enable', + 'WebMCP.cancelInvocation', + 'WebMCP.disable', + 'WebMCP.enable', + 'WebMCP.invokeTool', + 'WebMCP.toolInvoked', + 'WebMCP.toolResponded', + 'WebMCP.toolsAdded', + 'WebMCP.toolsRemoved', ] as const; export const cdpCapabilityCatalogue = { @@ -218,6 +227,7 @@ export const cdpCapabilityCatalogue = { 'DOM.childNodeCountUpdated': { kind: 'event', level: 'inspect' }, 'DOM.childNodeInserted': { kind: 'event', level: 'inspect' }, 'DOM.childNodeRemoved': { kind: 'event', level: 'inspect' }, + 'DOM.clearTextMarkers': { kind: 'command', level: 'inspect' }, 'DOM.collectClassNamesFromSubtree': { kind: 'command', level: 'inspect' }, 'DOM.copyTo': { kind: 'command', level: 'interact' }, 'DOM.describeNode': { kind: 'command', level: 'inspect' }, @@ -225,6 +235,7 @@ export const cdpCapabilityCatalogue = { 'DOM.distributedNodesUpdated': { kind: 'event', level: 'inspect' }, 'DOM.documentUpdated': { kind: 'event', level: 'inspect' }, 'DOM.focus': { kind: 'command', level: 'interact' }, + 'DOM.forceShowInterest': { kind: 'command', level: 'inspect' }, 'DOM.forceShowPopover': { kind: 'command', level: 'interact' }, 'DOM.getAnchorElement': { kind: 'command', level: 'inspect' }, 'DOM.getAttributes': { kind: 'command', level: 'inspect' }, @@ -237,6 +248,7 @@ export const cdpCapabilityCatalogue = { 'DOM.getFileInfo': { kind: 'command', level: 'inspect' }, 'DOM.getFlattenedDocument': { kind: 'command', level: 'inspect' }, 'DOM.getFrameOwner': { kind: 'command', level: 'inspect' }, + 'DOM.getImplicitAnchorCandidates': { kind: 'command', level: 'inspect' }, 'DOM.getNodeForLocation': { kind: 'command', level: 'inspect' }, 'DOM.getNodesForSubtreeByStyle': { kind: 'command', level: 'inspect' }, 'DOM.getNodeStackTraces': { kind: 'command', level: 'inspect' }, @@ -275,6 +287,7 @@ export const cdpCapabilityCatalogue = { 'DOM.setNodeStackTracesEnabled': { kind: 'command', level: 'debug' }, 'DOM.setNodeValue': { kind: 'command', level: 'interact' }, 'DOM.setOuterHTML': { kind: 'command', level: 'interact' }, + 'DOM.setTextMarker': { kind: 'command', level: 'inspect' }, 'DOM.shadowRootPopped': { kind: 'event', level: 'inspect' }, 'DOM.shadowRootPushed': { kind: 'event', level: 'inspect' }, 'DOM.topLayerElementsUpdated': { kind: 'event', level: 'inspect' }, @@ -305,6 +318,7 @@ export const cdpCapabilityCatalogue = { 'Emulation.screenOrientationLockChanged': { kind: 'event', level: 'observe' }, 'Emulation.setAutoDarkModeOverride': { kind: 'command', level: 'interact' }, 'Emulation.setAutomationOverride': { kind: 'command', level: 'interact' }, + 'Emulation.setCPUPerformanceOverride': { kind: 'command', level: 'interact' }, 'Emulation.setCPUThrottlingRate': { kind: 'command', level: 'interact' }, 'Emulation.setDataSaverOverride': { kind: 'command', level: 'interact' }, 'Emulation.setDefaultBackgroundColorOverride': { kind: 'command', level: 'interact' }, @@ -336,6 +350,7 @@ export const cdpCapabilityCatalogue = { 'Emulation.setTimezoneOverride': { kind: 'command', level: 'interact' }, 'Emulation.setTouchEmulationEnabled': { kind: 'command', level: 'interact' }, 'Emulation.setUserAgentOverride': { kind: 'command', level: 'interact' }, + 'Emulation.setVirtualKeyboardGeometryOverride': { kind: 'command', level: 'interact' }, 'Emulation.setVirtualTimePolicy': { kind: 'command', level: 'interact' }, 'Emulation.setVisibleSize': { kind: 'command', level: 'interact' }, 'Emulation.updateScreen': { kind: 'command', level: 'interact' }, @@ -377,7 +392,6 @@ export const cdpCapabilityCatalogue = { 'Network.canClearBrowserCache': { kind: 'command', level: 'observe' }, 'Network.canClearBrowserCookies': { kind: 'command', level: 'observe' }, 'Network.canEmulateNetworkConditions': { kind: 'command', level: 'observe' }, - 'Network.clearAcceptedEncodingsOverride': { kind: 'command', level: 'interact' }, 'Network.clearBrowserCache': { kind: 'command', level: 'interact' }, 'Network.clearBrowserCookies': { kind: 'command', level: 'interact' }, 'Network.configureDurableMessages': { kind: 'command', level: 'debug' }, @@ -429,7 +443,6 @@ export const cdpCapabilityCatalogue = { 'Network.responseReceivedEarlyHints': { kind: 'event', level: 'inspect' }, 'Network.responseReceivedExtraInfo': { kind: 'event', level: 'inspect' }, 'Network.searchInResponseBody': { kind: 'command', level: 'inspect' }, - 'Network.setAcceptedEncodings': { kind: 'command', level: 'interact' }, 'Network.setAttachDebugStack': { kind: 'command', level: 'debug' }, 'Network.setBlockedURLs': { kind: 'command', level: 'interact' }, 'Network.setBypassServiceWorker': { kind: 'command', level: 'interact' }, @@ -570,8 +583,10 @@ export const cdpCapabilityCatalogue = { 'Page.setTouchEmulationEnabled': { kind: 'command', level: 'interact' }, 'Page.setWebLifecycleState': { kind: 'command', level: 'interact' }, 'Page.startScreencast': { kind: 'command', level: 'debug' }, + 'Page.startScreenRecording': { kind: 'command', level: 'interact' }, 'Page.stopLoading': { kind: 'command', level: 'interact' }, 'Page.stopScreencast': { kind: 'command', level: 'debug' }, + 'Page.stopScreenRecording': { kind: 'command', level: 'interact' }, 'Page.waitForDebugger': { kind: 'command', level: 'debug' }, 'Page.windowOpen': { kind: 'event', level: 'inspect' }, 'Performance.getMetrics': { kind: 'command', level: 'inspect' }, @@ -621,14 +636,10 @@ export const cdpCapabilityCatalogue = { 'Storage.clearCookies': { kind: 'command', level: 'interact' }, 'Storage.clearDataForOrigin': { kind: 'command', level: 'interact' }, 'Storage.clearDataForStorageKey': { kind: 'command', level: 'interact' }, - 'Storage.clearSharedStorageEntries': { kind: 'command', level: 'interact' }, 'Storage.clearTrustTokens': { kind: 'command', level: 'interact' }, - 'Storage.deleteSharedStorageEntry': { kind: 'command', level: 'interact' }, 'Storage.deleteStorageBucket': { kind: 'command', level: 'interact' }, 'Storage.getCookies': { kind: 'command', level: 'inspect' }, 'Storage.getRelatedWebsiteSets': { kind: 'command', level: 'inspect' }, - 'Storage.getSharedStorageEntries': { kind: 'command', level: 'inspect' }, - 'Storage.getSharedStorageMetadata': { kind: 'command', level: 'inspect' }, 'Storage.getStorageKey': { kind: 'command', level: 'inspect' }, 'Storage.getStorageKeyForFrame': { kind: 'command', level: 'inspect' }, 'Storage.getTrustTokens': { kind: 'command', level: 'inspect' }, @@ -636,14 +647,9 @@ export const cdpCapabilityCatalogue = { 'Storage.indexedDBContentUpdated': { kind: 'event', level: 'inspect' }, 'Storage.indexedDBListUpdated': { kind: 'event', level: 'inspect' }, 'Storage.overrideQuotaForOrigin': { kind: 'command', level: 'interact' }, - 'Storage.resetSharedStorageBudget': { kind: 'command', level: 'interact' }, 'Storage.runBounceTrackingMitigations': { kind: 'command', level: 'interact' }, 'Storage.setCookies': { kind: 'command', level: 'interact' }, - 'Storage.setSharedStorageEntry': { kind: 'command', level: 'interact' }, - 'Storage.setSharedStorageTracking': { kind: 'command', level: 'debug' }, 'Storage.setStorageBucketTracking': { kind: 'command', level: 'debug' }, - 'Storage.sharedStorageAccessed': { kind: 'event', level: 'inspect' }, - 'Storage.sharedStorageWorkletOperationExecutionFinished': { kind: 'event', level: 'inspect' }, 'Storage.storageBucketCreatedOrUpdated': { kind: 'event', level: 'inspect' }, 'Storage.storageBucketDeleted': { kind: 'event', level: 'inspect' }, 'Storage.trackCacheStorageForOrigin': { kind: 'command', level: 'debug' }, diff --git a/packages/core/src/client-target-connection.ts b/packages/core/src/client-target-connection.ts index 64d4991..9955d5f 100644 --- a/packages/core/src/client-target-connection.ts +++ b/packages/core/src/client-target-connection.ts @@ -47,6 +47,7 @@ export async function connectStoreBackedClientTargetBroker( broker: TargetBroker, options: StoreBackedClientTargetConnectionOptions, ): Promise<() => void> { + defineClientConnection(connection); let available = true; let record: AuthorityRecord; try { @@ -236,3 +237,8 @@ export function connectClientTargetBroker(connection: ClientTargetConnection, br void iterator.return?.(); } } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineClientConnection(definition: Definition): Definition { + return definition; +} diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 5b34b2a..00461e2 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -1,5 +1,8 @@ import type { AcquireLeaseRequest, ArtifactAccessRequest, CdpSubscription, ReleaseLeaseRequest, RenewLeaseRequest } from './broker.js'; import type { CdpCommand, CdpCommandResult, CdpSubscriptionRequest, Lease, PublishedTarget, TargetRevocationReason } from './protocol.js'; +import type { WebMcpClient } from './webmcp.js'; + +import { createWebMcpClient } from './webmcp.js'; export type TargetChange = | { readonly kind: 'published'; readonly sequence: number; readonly target: PublishedTarget } @@ -33,7 +36,7 @@ export interface ClientFacadeAdapter { readonly directory: TargetDirectory; } -export interface ChromeDebuggerBridgeClient { +export interface ChromeDebuggerBridgeClient extends WebMcpClient { acquireLease: (request: AcquireLeaseRequest) => Promise; executeCommand: (command: CdpCommand) => Promise; listTargets: () => Promise; @@ -54,6 +57,12 @@ export function createClientFacadeAdapter(directory: TargetDirectory): ClientFac export function createChromeDebuggerBridgeClient(adapter: ClientFacadeAdapter): ChromeDebuggerBridgeClient { const { directory } = adapter; return { + ...createWebMcpClient({ + async executeCommand(command) { + if (directory.executeCommand === undefined) throw new Error('The target directory does not support CDP commands.'); + return directory.executeCommand(command); + }, + }), async acquireLease(request) { if (directory.acquireLease === undefined) { throw new Error('The target directory does not support leases.'); diff --git a/packages/core/src/diagnostic-trace.ts b/packages/core/src/diagnostic-trace.ts index 1675a6d..e8e9675 100644 --- a/packages/core/src/diagnostic-trace.ts +++ b/packages/core/src/diagnostic-trace.ts @@ -9,7 +9,10 @@ export type DiagnosticCode | 'SESSION_NOT_FOUND' | 'TARGET_GENERATION_STALE' | 'TARGET_NOT_FOUND' - | 'TARGET_REVOKED'; + | 'TARGET_REVOKED' + | 'WEBMCP_DISCOVERY_FAILED' + | 'WEBMCP_TOOL_STALE' + | 'WEBMCP_OUTCOME_UNKNOWN'; export interface DiagnosticTraceEntry { readonly code: DiagnosticCode; diff --git a/packages/core/src/embedded.ts b/packages/core/src/embedded.ts index a51ca62..7a53b7e 100644 --- a/packages/core/src/embedded.ts +++ b/packages/core/src/embedded.ts @@ -3,7 +3,7 @@ import type { AcquireLeaseRequest, ArtifactAccessRequest, CdpSubscription, Creat import type { ChromeDebuggerBridgeClient, TargetDirectory } from './client.js'; import type { CdpCommand, CdpSubscriptionRequest, JsonObject, Lease, PublishedTarget } from './protocol.js'; -import { createTargetBroker, TargetBrokerError } from './broker.js'; +import { createTargetBroker, defineTargetBroker, TargetBrokerError } from './broker.js'; import { createChromeDebuggerBridgeClient, createClientFacadeAdapter } from './client.js'; /** Applies host-specific authorization in addition to the broker's mandatory capability checks. */ @@ -45,6 +45,7 @@ function copy(value: Value): Value { * boundary so host adapters cannot share mutable protocol values with broker state. */ export function createEmbeddedChromeDebuggerBridge(options: CreateEmbeddedChromeDebuggerBridgeOptions = {}): EmbeddedChromeDebuggerBridge { + defineEmbeddedBridge(options); const broker = createTargetBroker(options); const pendingOperationRejectors = new Set<(error: Error) => void>(); const closeClientResources = new Set<() => void>(); @@ -186,3 +187,9 @@ export function createEmbeddedChromeDebuggerBridge(options: CreateEmbeddedChrome }, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineEmbeddedBridge(definition: Definition): Definition { + defineTargetBroker(definition); + return definition; +} diff --git a/packages/core/src/grant-request.ts b/packages/core/src/grant-request.ts index fd41440..c54a664 100644 --- a/packages/core/src/grant-request.ts +++ b/packages/core/src/grant-request.ts @@ -111,6 +111,7 @@ interface StoredGrantRequest { export function createGrantRequestCoordinator( options: CreateGrantRequestCoordinatorOptions, ): GrantRequestCoordinator { + defineGrantRequests(options); const requests = new Map(); const listeners = new Set<(change: GrantRequestChange) => void>(); const timing = { ...defaultGrantRequestTimingPolicy, ...options.timing }; @@ -418,3 +419,9 @@ export function createGrantRequestCoordinator( }, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineGrantRequests(definition: Definition): Definition { + validateTimeoutMilliseconds({ ...defaultGrantRequestTimingPolicy, ...definition.timing }.requestTimeoutMilliseconds, 'requestTimeoutMilliseconds'); + return definition; +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 26df16d..66648a6 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,3 +1,4 @@ +export { defineAgentConnection } from './agent-target-connection.js'; export { type AgentConnectionTimingPolicy, type AgentTargetConnection, @@ -48,6 +49,7 @@ export { type AutomationTextMatcher, requiredAutomationLevel, } from './automation.js'; +export { defineTargetBroker } from './broker.js'; export { type AcquireLeaseRequest, type ArtifactAccessRequest, @@ -61,6 +63,7 @@ export { type RenewLeaseRequest, type TargetBroker, } from './broker.js'; +export { defineClientConnection } from './client-target-connection.js'; export { type ClientTargetConnection, connectClientTargetBroker, @@ -81,6 +84,7 @@ export { type DiagnosticTraceEntry, type DiagnosticTraceStore, } from './diagnostic-trace.js'; +export { defineEmbeddedBridge } from './embedded.js'; export { createEmbeddedChromeDebuggerBridge, type CreateEmbeddedChromeDebuggerBridgeOptions, @@ -88,6 +92,7 @@ export { type EmbeddedChromeDebuggerBridge, type EmbeddedChromeDebuggerBridgeClient, } from './embedded.js'; +export { defineGrantRequests } from './grant-request.js'; export { createGrantRequestCoordinator, type CreateGrantRequestCoordinatorOptions, @@ -105,6 +110,7 @@ export { type GrantRequestTimingPolicy, } from './grant-request.js'; export * from './protocol.js'; +export { defineLogicalSession } from './session.js'; export { createLogicalSessionManager, type CreateLogicalSessionManagerOptions, @@ -119,3 +125,5 @@ export { type StoredCredential, } from './session.js'; export { scheduleTimeout, type TimeoutMilliseconds, validateTimeoutMilliseconds } from './timing.js'; + +export { createWebMcpClient, type WebMcpClient, WebMcpError, type WebMcpInvocation, webMcpMethods, type WebMcpRequest, type WebMcpResult, type WebMcpTool, type WebMcpToolList, type WebMcpToolResult } from './webmcp.js'; diff --git a/packages/core/src/protocol/domain.ts b/packages/core/src/protocol/domain.ts index 64bba2f..b47c517 100644 --- a/packages/core/src/protocol/domain.ts +++ b/packages/core/src/protocol/domain.ts @@ -179,6 +179,9 @@ export const bridgeErrorCodeSchemaDefinition = z.enum([ 'TARGET_NOT_FOUND', 'TARGET_REVOKED', 'TRANSPORT_CLOSED', + 'WEBMCP_DISCOVERY_FAILED', + 'WEBMCP_TOOL_STALE', + 'WEBMCP_OUTCOME_UNKNOWN', ]); export const bridgeErrorSchemaDefinition = z.strictObject({ diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index bb44cad..ed7295c 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -143,6 +143,7 @@ function jsonMetadata(metadata: JsonValue | undefined): JsonValue | undefined { export function createLogicalSessionManager( options: CreateLogicalSessionManagerOptions & { readonly authorityStore: AuthorityStore }, ): LogicalSessionManager { + defineLogicalSession(options); const authorityStore = options.authorityStore; const generateCredential = options.generateCredential ?? randomCredential; const generateId = options.generateId ?? (() => crypto.randomUUID()); @@ -269,3 +270,9 @@ export function createLogicalSessionManager( }, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineLogicalSession(definition: Definition): Definition { + validateTimeoutMilliseconds({ ...defaultLogicalSessionTimingPolicy, ...definition.timing }.resumeWindowMilliseconds, 'resumeWindowMilliseconds'); + return definition; +} diff --git a/packages/core/src/webmcp.ts b/packages/core/src/webmcp.ts new file mode 100644 index 0000000..7b3cf41 --- /dev/null +++ b/packages/core/src/webmcp.ts @@ -0,0 +1,75 @@ +import type { CdpCommand, CdpCommandResult, JsonObject, JsonValue } from './protocol.js'; + +/** Address one authorized main document through an existing lease. */ +export type WebMcpRequest = Pick; + +export interface WebMcpTool { + readonly name: string; + readonly description: string; + readonly inputSchema?: JsonObject; + readonly annotations?: JsonObject; + /** Bound to the target generation and main document, not a grant of authority. */ + readonly toolRef: string; +} + +export interface WebMcpToolList { + readonly documentRef: string; + readonly enabled: boolean; + readonly tools: readonly WebMcpTool[]; +} + +export type WebMcpInvocation = { + readonly input: JsonObject; +} & ( + | { readonly toolName: string; readonly toolRef?: never } + | { readonly toolName?: never; readonly toolRef: string } +); + +export interface WebMcpToolResult { + readonly status: 'completed'; + readonly output?: JsonValue; +} + +/** Large results use the ordinary artifact envelope and retain the caller's lease. */ +export type WebMcpResult = Omit & { + readonly value: Value | { readonly artifact: import('./protocol.js').ArtifactDescriptor }; +}; + +export interface WebMcpClient { + listWebMcpTools: (request: WebMcpRequest) => Promise>; + /** Invokes one tool. Discovery exclusions do not deny direct invocation by name. */ + invokeWebMcpTools: (request: WebMcpRequest & WebMcpInvocation) => Promise>; +} + +export const webMcpMethods = { + list: 'Bridge.listWebMcpTools', + invoke: 'Bridge.invokeWebMcpTools', +} as const; + +/** Preserves actionable failures across the extension and broker transport boundaries. */ +export class WebMcpError extends Error { + constructor( + readonly code: 'FEATURE_UNSUPPORTED' | 'WEBMCP_DISCOVERY_FAILED' | 'WEBMCP_TOOL_STALE' | 'WEBMCP_OUTCOME_UNKNOWN' | 'CDP_COMMAND_FAILED' | 'REQUEST_CANCELLED', + message: string, + readonly retryable = false, + ) { + super(message); + } +} + +/** Adds WebMCP calls to an authenticated command transport without creating a connection. */ +export function createWebMcpClient(client: { executeCommand: (command: CdpCommand) => Promise }): WebMcpClient { + return { + async listWebMcpTools(request) { + return client.executeCommand({ ...request, method: webMcpMethods.list }) as Promise>; + }, + async invokeWebMcpTools(request) { + const { input, toolName, toolRef, ...authority } = request; + return client.executeCommand({ + ...authority, + method: webMcpMethods.invoke, + parameters: { input, ...(toolName === undefined ? { toolRef } : { toolName }) }, + }) as Promise>; + }, + }; +} diff --git a/packages/core/test/cdp-catalogue.test.ts b/packages/core/test/cdp-catalogue.test.ts index 1e5b5c5..5ffa3d9 100644 --- a/packages/core/test/cdp-catalogue.test.ts +++ b/packages/core/test/cdp-catalogue.test.ts @@ -37,7 +37,7 @@ describe('generated CDP capability catalogue', () => { it('records the pinned source and Chrome debugger domain scope', () => { expect.assertions(4); - expect(cdpCatalogueMetadata.devtoolsProtocolVersion).toBe('0.0.1672245'); + expect(cdpCatalogueMetadata.devtoolsProtocolVersion).toBe('0.0.1698617'); expect(cdpCatalogueMetadata.protocolVersion).toBe('1.3'); expect(cdpCatalogueMetadata.supportedDomains).toContain('Target'); expect(cdpCatalogueMetadata.absentSupportedDomains).toEqual(['Database']); diff --git a/packages/core/test/config.test.ts b/packages/core/test/config.test.ts new file mode 100644 index 0000000..7f5f6f0 --- /dev/null +++ b/packages/core/test/config.test.ts @@ -0,0 +1,25 @@ +import { expect, it, vi } from 'vitest'; + +import { defineAgentConnection } from '../src/agent-target-connection.js'; +import { createTargetBroker, defineTargetBroker } from '../src/broker.js'; +import { defineEmbeddedBridge } from '../src/embedded.js'; + +it('validates broker configuration without allocating runtime resources', () => { + expect.assertions(5); + const generateId = vi.fn(); + const now = vi.fn(); + const definition = { generateId, now, timing: { commandTimeoutMilliseconds: null } }; + expect(defineTargetBroker(definition)).toBe(definition); + expect(defineEmbeddedBridge(definition)).toBe(definition); + expect(generateId).not.toHaveBeenCalled(); + expect(now).not.toHaveBeenCalled(); + expect(defineTargetBroker(definition).timing.commandTimeoutMilliseconds).toBeNull(); +}); + +it('shares invalid timing behavior with construction and connection helpers', () => { + expect.assertions(3); + const definition = { timing: { commandTimeoutMilliseconds: -1 } }; + expect(() => defineTargetBroker(definition)).toThrow('commandTimeoutMilliseconds'); + expect(() => createTargetBroker(definition)).toThrow('commandTimeoutMilliseconds'); + expect(() => defineAgentConnection({ timing: { heartbeatIntervalMilliseconds: 0 } })).toThrow('heartbeatIntervalMilliseconds'); +}); diff --git a/packages/core/test/scaffold.test.ts b/packages/core/test/scaffold.test.ts index d21ea3f..ccd60a6 100644 --- a/packages/core/test/scaffold.test.ts +++ b/packages/core/test/scaffold.test.ts @@ -9,7 +9,7 @@ import * as testingModule from '../src/testing.js'; it('keeps the core scaffold importable', () => { expect.assertions(5); expect(Object.keys(indexModule)).toEqual(expect.arrayContaining(Object.keys(protocolModule))); - expect(Object.keys(brokerModule)).toEqual(['TargetBrokerError', 'defaultBrokerTimingPolicy', 'createTargetBroker']); + expect(Object.keys(brokerModule)).toEqual(['TargetBrokerError', 'defaultBrokerTimingPolicy', 'createTargetBroker', 'defineTargetBroker']); expect(Object.keys(clientModule)).toEqual(['createClientFacadeAdapter', 'createChromeDebuggerBridgeClient']); expect(Object.keys(protocolModule)).not.toEqual([]); expect(Object.keys(testingModule)).toEqual([]); diff --git a/packages/devframe/README.md b/packages/devframe/README.md index 87a9b23..070f1a1 100644 --- a/packages/devframe/README.md +++ b/packages/devframe/README.md @@ -43,19 +43,30 @@ const panel = createCdbPanel({ }); ``` -The renderer must support Devframe's JSON view and action contracts. Component overrides apply only to that mounted renderer; the standalone SPA uses the reference catalogue. Dock ordering follows the host's categories and saved preferences. The page script remains available through `dock.clientScript` for host notifications and review intents. +The renderer must support Devframe's JSON view and action contracts. Component overrides apply only to that mounted renderer; the standalone SPA uses the reference catalogue. Dock ordering follows the host's categories and saved preferences. The page script remains available through `dock.clientScript` for tab-local notification commands and review intents. For an existing application container, import `mountBrowserControlPanel` from `@dvcol/cdb-devframe/panel` and pass a `client`, `container`, optional branding/CSS and an `onReview` callback. Unmounting releases the panel subscription without disposing that client. -The page script emits `cdb:review-request` with `{ requestId }` to request the embedding application's final approval UI. This is an untrusted presentation intent. It never grants authority or proves a human decision. The extension must authenticate final approval from its trusted popup or other host-approved channel. +The page script emits `cdb:review-request` with a `BrowserControlApprovalEventDetail`. This is an untrusted presentation intent. It never grants authority or proves a human decision. The extension must authenticate final approval from its trusted popup or another host-approved channel, then register its result synchronously: -Hosts with a direct approval channel can set `approvalAction: 'accept'` on `createCdbPanel`. The page script then labels its action **Accept** and emits `cdb:accept-request`; the embedding application authenticates and handles the final approval. The default remains **Review request** and `cdb:review-request`. +```ts +import type { BrowserControlApprovalEventDetail } from '@dvcol/cdb-devframe/page-script'; + +window.addEventListener('cdb:review-request', (event) => { + const detail = (event as CustomEvent).detail; + detail.respondWith(openTrustedApproval(detail.requestId)); +}); +``` + +`respondWith` accepts a `PromiseLike`. The page command awaits it and preserves structured rejection fields: `code`, `message`, `retryable`, `retryAfterMilliseconds`, and `details`. A missing synchronous responder fails with `APPROVAL_HANDLER_UNAVAILABLE`. `CdbPanelOptions.onNotificationError` receives the same `BrowserControlErrorData` shape when shared message creation, update, or dismissal fails. + +Hosts with a direct approval channel can set `approvalAction: 'accept'` on `createCdbPanel`. The page script then labels its action **Accept** and emits `cdb:accept-request` with the same awaitable detail. The default remains **Review request** and `cdb:review-request`. See the [runnable example](../../examples/devframe/README.md). -Notification descriptions and approved-tab counts update through the existing Devframe message handle. Changed content retains its message ID and follows the host's normal notification behavior, including resurfacing a dismissed toast. Unchanged broker publications do not update the message. Request completion, expiry and disposal remove its message and command. +The panel host publishes one notification per request or approved scope into Devframe’s shared message feed. Each viewing tab registers the matching command locally, so approval executes in the tab where the user clicks it. Closing a tab releases its command registrations without removing the shared message. -The public validation workspace backports Devframe's toast-removal fix to hub-ui 0.9.10 using [a temporary pnpm patch](https://github.com/dvcol/chrome-debugger-bridge/blob/main/patches/README.md). This workspace patch is not inherited by consumers of the published CDB package; embedding applications using that hub-ui version must apply the patch themselves until adopting an upstream version containing the fix. +Notification descriptions and approved-tab counts update through the existing Devframe message handle. Changed content retains its message ID and follows the host's normal notification behavior, including resurfacing a dismissed toast. Unchanged broker publications do not update the message. Request completion, expiry and panel-host disposal remove the shared message. Each page removes its command when its subscription reports that the request or scope ended. ### Connection-bound clients diff --git a/packages/devframe/src/client.ts b/packages/devframe/src/client.ts index 885427e..d13b793 100644 --- a/packages/devframe/src/client.ts +++ b/packages/devframe/src/client.ts @@ -52,11 +52,8 @@ export interface CdbClientSession { } /** Retains and rotates one principal's resume credential; hosts forward connection lifecycle events. */ -export function createCdbClientSession(options: { - readonly credentialKey: string; - readonly credentialStore?: CredentialStore; - readonly metadata?: JsonValue; -}): CdbClientSession { +export function createCdbClientSession(options: CdbClientSessionOptions): CdbClientSession { + defineClientSession(options); const store = options.credentialStore ?? createMemoryCredentialStore(); const input = options.metadata === undefined ? {} : { metadata: options.metadata }; let pending = Promise.resolve(); @@ -159,6 +156,7 @@ export function createCdbClient(client: CdbDevframeClient): CdbClient { } }, async connectProvider(options: ConnectCdbProviderOptions): Promise { + defineProvider(options); const { brokerId } = await call<{ readonly brokerId: string }>('provider-register', options.registration); let pairing = await options.pairingStore.load(options.pairingKey); pairing ??= await options.pairingStore.findByIdentity?.(brokerId, options.registration.instanceId); @@ -255,3 +253,20 @@ export function createCdbClient(client: CdbDevframeClient): CdbClient { clients.set(client, handle); return handle; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineClientSession(definition: Definition): Definition { + return definition; +} + +/** Configuration accepted by createCdbClientSession and defineClientSession. */ +export interface CdbClientSessionOptions { + readonly credentialKey: string; + readonly credentialStore?: CredentialStore; + readonly metadata?: JsonValue; +} + +/** Defines provider connection configuration without reading credentials or connecting. */ +export function defineProvider(definition: Definition): Definition { + return definition; +} diff --git a/packages/devframe/src/connection.ts b/packages/devframe/src/connection.ts index b33ecd6..5cacc61 100644 --- a/packages/devframe/src/connection.ts +++ b/packages/devframe/src/connection.ts @@ -44,6 +44,7 @@ async function untilAborted(pending: Promise, signal: AbortSigna /** One handle per host/principal. It never opens or closes the supplied transport. */ export function createCdbConnection(options: CdbConnectionOptions = {}): CdbConnection { + defineConnection(options); const session = options.session === undefined ? undefined : createCdbClientSession(options.session); const listeners = new Set<(state: BrokerState) => void>(); const cleanups = new Set>(); @@ -183,3 +184,8 @@ export function createCdbConnection(options: CdbConnectionOptions = {}): CdbConn }, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineConnection(definition: Definition): Definition { + return definition; +} diff --git a/packages/devframe/src/index.ts b/packages/devframe/src/index.ts index 5ef0f1d..4cdb626 100644 --- a/packages/devframe/src/index.ts +++ b/packages/devframe/src/index.ts @@ -1,4 +1,6 @@ +export { definePanel } from './panel-definition.js'; export { type CdbPanel, type CdbPanelOptions, createCdbPanel } from './panel-definition.js'; export { type BrowserControlPanelComponents, buildBrowserControlPanelView } from './panel-view.js'; +export { defineService } from './service.js'; export { type CdbDevframePeerSession, type CdbDevframeService, type CdbInvocation, type CdbServiceOptions, createCdbService, createCdbService as default, getCdbService } from './service.js'; export { cdbServiceScope } from './wire.js'; diff --git a/packages/devframe/src/notification-items.ts b/packages/devframe/src/notification-items.ts new file mode 100644 index 0000000..95a316d --- /dev/null +++ b/packages/devframe/src/notification-items.ts @@ -0,0 +1,48 @@ +import type { BrowserControlNotification } from '@dvcol/cdb-extension/notifications'; + +/** One command per action; a page-local command registration resolves each action independently. */ +export interface BrowserControlNotificationAction { + readonly id: 'review' | 'accept' | 'reject' | 'revoke'; + readonly label: string; +} + +export interface BrowserControlNotificationItem { + readonly id: string; + readonly requestId: string; + readonly kind: 'request' | 'grant'; + readonly title: string; + readonly description: string; + readonly actions: readonly BrowserControlNotificationAction[]; +} + +/** Shared message IDs resolve to a command installed independently in each viewing tab. */ +export function browserControlNotificationItems(state: BrowserControlNotification, approvalAction: 'review' | 'accept' = 'review'): BrowserControlNotificationItem[] { + const items: BrowserControlNotificationItem[] = state.requests.map(request => ({ + id: `cdb:browser-control:request:${request.id}`, + requestId: request.id, + kind: 'request', + title: 'Browser control requested', + description: `${request.principalLabel} requests ${request.level} access with ${request.navigation} navigation.`, + actions: [ + { id: approvalAction, label: approvalAction === 'accept' ? 'Accept' : 'Review request' }, + { id: 'reject', label: 'Reject' }, + ], + })); + for (const [requestId, grants] of Map.groupBy(state.grants, grant => grant.requestId)) { + const grant = grants[0]!; + const tabLabel = grants.length === 1 ? 'tab' : 'tabs'; + items.push({ + id: `cdb:browser-control:grant:${requestId}`, + requestId, + kind: 'grant', + title: 'Browser control active', + description: `${grant.principalLabel}: ${grant.level} access to ${grants.length} approved ${tabLabel}.`, + actions: [{ id: 'revoke', label: 'Stop control' }], + }); + } + return items; +} + +export interface BrowserControlMessages { + info: (message: string, options: { id: string; description: string; notify: boolean; autoDismiss: false; actions: { id: string; label: string; kind: 'command'; command: { id: string } }[] }) => Promise<{ dismiss: () => Promise; update: (patch: { description: string }) => Promise }>; +} diff --git a/packages/devframe/src/notification-publisher.ts b/packages/devframe/src/notification-publisher.ts new file mode 100644 index 0000000..6bcd4cc --- /dev/null +++ b/packages/devframe/src/notification-publisher.ts @@ -0,0 +1,83 @@ +import type { BrowserControlErrorData } from '@dvcol/cdb-broker/contract'; +import type { BrowserControlNotificationController } from '@dvcol/cdb-extension/notifications'; + +import type { BrowserControlMessages, BrowserControlNotificationItem } from './notification-items.js'; + +import { styleText } from 'node:util'; + +import { normalizeBrowserControlError } from '@dvcol/cdb-broker/contract'; + +import { browserControlNotificationItems } from './notification-items.js'; + +/** The panel host owns shared messages; page clients own only their local command registrations. */ +export function publishBrowserControlNotifications( + controller: BrowserControlNotificationController, + messages: BrowserControlMessages, + approvalAction: 'review' | 'accept' = 'review', + onError?: (error: BrowserControlErrorData) => void, +): () => void { + const notifications = new Map void; remove: () => void }>(); + const pending = new Map>(); + + /** Keep removal ordered before re-creation of the same ID during connection replacement. */ + function enqueue(id: string, operation: () => Promise): void { + const next = (pending.get(id) ?? Promise.resolve()).then(operation).catch((error) => { + const failure = normalizeBrowserControlError(error); + if (onError === undefined) console.error(styleText('red', '❌ [cdb]'), 'Unable to publish browser-control notification.', failure); + else onError(failure); + }); + pending.set(id, next); + void next.then(() => { + if (pending.get(id) === next) pending.delete(id); + }); + } + + function createNotification(item: BrowserControlNotificationItem): void { + let active = true; + let handle: Awaited> | undefined; + enqueue(item.id, async () => { + if (!active) return; + handle = await messages.info(item.title, { id: item.id, description: item.description, notify: true, autoDismiss: false, actions: item.actions.map(action => ({ id: action.id, label: action.label, kind: 'command', command: { id: `${item.id}:${action.id}` } })) }); + }); + notifications.set(item.id, { + description: item.description, + update(description) { + enqueue(item.id, async () => { + if (active) await handle?.update({ description }); + }); + }, + remove() { + active = false; + enqueue(item.id, async () => { + await handle?.dismiss(); + }); + }, + }); + } + + const unsubscribe = controller.subscribe((state) => { + const items = browserControlNotificationItems(state, approvalAction); + const activeIds = new Set(items.map(item => item.id)); + for (const [id, notification] of notifications) { + if (activeIds.has(id)) continue; + notification.remove(); + notifications.delete(id); + } + for (const item of items) { + const notification = notifications.get(item.id); + if (notification === undefined) { + createNotification(item); + continue; + } + if (notification.description === item.description) continue; + notification.description = item.description; + notification.update(item.description); + } + }); + + return function dispose(): void { + unsubscribe(); + for (const notification of notifications.values()) notification.remove(); + notifications.clear(); + }; +} diff --git a/packages/devframe/src/page-script.ts b/packages/devframe/src/page-script.ts index 7259925..d9108b5 100644 --- a/packages/devframe/src/page-script.ts +++ b/packages/devframe/src/page-script.ts @@ -1,13 +1,21 @@ +import type { BrowserControlErrorData } from '@dvcol/cdb-broker/contract'; import type { DevframeRpcClient } from 'devframe/client'; +import { normalizeBrowserControlError } from '@dvcol/cdb-broker/contract'; import { createBrowserControlNotificationController } from '@dvcol/cdb-extension/notifications'; +import { browserControlNotificationItems } from './notification-items.js'; import { createBrowserControlPanelClient } from './panel.js'; /** Page events request the host's final approval UI. They never approve browser access. */ export const browserControlReviewEvent = 'cdb:review-request'; export const browserControlAcceptEvent = 'cdb:accept-request'; +export interface BrowserControlApprovalEventDetail { + readonly requestId: string; + respondWith: (response: PromiseLike) => void; +} + export interface BrowserControlPageOptions { /** The embedding application owns the final approval channel for either action. */ readonly approvalAction?: 'review' | 'accept'; @@ -22,73 +30,87 @@ export async function setupBrowserControlAcceptPage(context: BrowserControlPageC export interface BrowserControlPageContext { readonly rpc: DevframeRpcClient; - readonly current: { readonly domElements: { readonly iframe?: HTMLIFrameElement | null } }; readonly commands: { register: (command: { id: string; title: string; source: 'client'; action: () => Promise; showInPalette: boolean }) => () => void; }; - readonly messages: { - info: (message: string, options: { id: string; description: string; notify: boolean; autoDismiss: false; actions: { id: string; label: string; kind: 'command'; command: { id: string } }[] }) => Promise<{ dismiss: () => Promise; update: (patch: { description: string }) => Promise }>; - }; +} + +function commandError(error: unknown): Error & BrowserControlErrorData { + const result = normalizeBrowserControlError(error); + return Object.assign(new Error(result.message), result); } /** Uses the hub's existing page connection; the embedding extension handles the review intent. */ export default async function setupBrowserControlPage(context: BrowserControlPageContext, options: BrowserControlPageOptions = {}): Promise<() => void> { + definePage(options); const client = createBrowserControlPanelClient(context.rpc); let disposed = false; let available = false; let stopHostBindings: (() => void) | undefined; - const review = (requestId: string): void => { - if (disposed || !available) return; - window.dispatchEvent(new CustomEvent(options.approvalAction === 'accept' ? browserControlAcceptEvent : browserControlReviewEvent, { detail: { requestId } })); + const review = async (requestId: string): Promise => { + if (disposed || !available) throw commandError({ code: 'PROVIDER_UNAVAILABLE', message: 'Browser approval is unavailable.', retryable: false }); + let acceptingResponse = true; + let response: PromiseLike | undefined; + let responseError: Error | undefined; + const detail: BrowserControlApprovalEventDetail = { + requestId, + respondWith(value) { + if (!acceptingResponse) { + responseError = new Error('The approval response must be registered during event dispatch.'); + return; + } + if (response !== undefined) { + responseError = new Error('The approval event already has a response.'); + return; + } + response = value; + }, + }; + window.dispatchEvent(new CustomEvent( + options.approvalAction === 'accept' ? browserControlAcceptEvent : browserControlReviewEvent, + { detail }, + )); + acceptingResponse = false; + if (responseError !== undefined) throw commandError(responseError); + if (response === undefined) throw commandError({ code: 'APPROVAL_HANDLER_UNAVAILABLE', message: 'No browser approval handler responded.', retryable: false }); + try { + await response; + } catch (error) { + throw commandError(error); + } }; - const controller = createBrowserControlNotificationController({ onReview: request => review(request.id), onRevoke: async requestId => client.revokeScope(requestId) }); - const notifications = new Map void; remove: () => void }>(); - const prefix = `cdb:browser-control:${crypto.randomUUID()}`; + const controller = createBrowserControlNotificationController({ + onReview: async request => review(request.id), + onReject: async requestId => client.revokeScope(requestId), + onRevoke: async requestId => client.revokeScope(requestId), + }); + const commands = new Map void>(); const stopNotifications = controller.subscribe((state) => { - const items = [ - ...state.requests.map(request => ({ id: `request:${request.id}`, title: 'Browser control requested', description: `${request.principalLabel} requests ${request.level} access with ${request.navigation} navigation.`, label: options.approvalAction === 'accept' ? 'Accept' : 'Review request', action: async () => controller.review(request.id) })), - ...Array.from(Map.groupBy(state.grants, grant => grant.requestId), ([requestId, grants]) => ({ id: `grant:${requestId}`, title: 'Browser control active', description: `${grants[0]!.principalLabel}: ${grants[0]!.level} access to ${grants.length} approved ${grants.length === 1 ? 'tab' : 'tabs'}.`, label: 'Stop control', action: async () => controller.revoke(requestId) })), - ]; - for (const [id, notification] of notifications) { - if (items.some(item => item.id === id)) continue; - notification.remove(); - notifications.delete(id); + const items = browserControlNotificationItems(state, options.approvalAction); + const activeIds = new Set(items.flatMap(item => item.actions.map(action => `${item.id}:${action.id}`))); + for (const [id, unregister] of commands) { + if (activeIds.has(id)) continue; + unregister(); + commands.delete(id); } for (const item of items) { - const notification = notifications.get(item.id); - if (notification !== undefined) { - if (notification.description !== item.description) { - notification.description = item.description; - notification.update(item.description); + for (const action of item.actions) { + const commandId = `${item.id}:${action.id}`; + if (commands.has(commandId)) continue; + async function invoke(): Promise { + try { + if (action.id === 'accept' || action.id === 'review') return await controller.review(item.requestId); + if (action.id === 'reject') return await controller.reject(item.requestId); + await controller.revoke(item.requestId); + } catch (error) { + throw commandError(error); + } } - continue; + const unregister = context.commands.register({ id: commandId, title: action.label, source: 'client', action: invoke, showInPalette: false }); + commands.set(commandId, unregister); } - const id = `${prefix}:${item.id}`; - const unregister = context.commands.register({ id, title: item.label, source: 'client', action: item.action, showInPalette: false }); - const message = context.messages.info(item.title, { id, description: item.description, notify: true, autoDismiss: false, actions: [{ id: 'control', label: item.label, kind: 'command', command: { id } }] }).catch(error => console.error('Unable to show browser-control notification.', error)); - let active = true; - let pending = Promise.resolve(); - notifications.set(item.id, { - description: item.description, - update(description) { - pending = pending.then(async () => { - const handle = await message; - if (active) await handle?.update({ description }); - }).catch(error => console.error('Unable to update browser-control notification.', error)); - }, - remove() { - active = false; - unregister(); - pending = pending.then(async () => (await message)?.dismiss()).catch(error => console.error('Unable to dismiss browser-control notification.', error)); - }, - }); } }); - const receive = (event: MessageEvent): void => { - if (event.source !== context.current.domElements.iframe?.contentWindow || event.source === null) return; - const data = event.data; - if (data !== null && typeof data === 'object' && 'type' in data && data.type === browserControlReviewEvent && 'requestId' in data && typeof data.requestId === 'string') review(data.requestId); - }; let stopWatching: (() => void) | undefined; const dispose = (): void => { if (disposed) return; @@ -98,10 +120,9 @@ export default async function setupBrowserControlPage(context: BrowserControlPag stopWatching?.(); document.documentElement.removeAttribute('data-cdb-notifications-ready'); stopNotifications(); - for (const notification of notifications.values()) notification.remove(); - notifications.clear(); + for (const unregister of commands.values()) unregister(); + commands.clear(); controller.dispose(); - window.removeEventListener('message', receive); window.removeEventListener('pagehide', dispose); }; window.addEventListener('pagehide', dispose, { once: true }); @@ -123,7 +144,6 @@ export default async function setupBrowserControlPage(context: BrowserControlPag stopWatching(); return dispose; } - window.addEventListener('message', receive); return dispose; } catch (error) { dispose(); @@ -132,3 +152,8 @@ export default async function setupBrowserControlPage(context: BrowserControlPag } export { createPageRequestBridge, type PageRequestBridge, type PageRequestBridgeOptions, type PageRequestOptions } from '@dvcol/cdb-extension'; + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function definePage(definition: Definition): Definition { + return definition; +} diff --git a/packages/devframe/src/panel-definition.ts b/packages/devframe/src/panel-definition.ts index d42cb4e..d946b36 100644 --- a/packages/devframe/src/panel-definition.ts +++ b/packages/devframe/src/panel-definition.ts @@ -1,8 +1,9 @@ import type { DevframeHubContext } from '@devframes/hub'; import type { JsonRenderView } from '@devframes/json-render'; -import type { BrokerState } from '@dvcol/cdb-broker/contract'; +import type { BrokerState, BrowserControlErrorData } from '@dvcol/cdb-broker/contract'; import type { DevframeDefinition, DevframeNodeContext, DevframeScopedNodeRpc } from 'devframe'; +import type { BrowserControlMessages } from './notification-items.js'; import type { BrowserControlPanelComponents } from './panel-view.js'; import type { BrowserControlPanelClient } from './panel.js'; @@ -12,12 +13,16 @@ import { fileURLToPath } from 'node:url'; import { jsonRenderSpaDir } from '@devframes/json-render-ui/spa'; import { toJsonRenderDockEntry } from '@devframes/json-render/hub'; import { createJsonRenderView } from '@devframes/json-render/node'; +import { createBrowserControlNotificationController } from '@dvcol/cdb-extension/notifications'; import packageManifest from '../package.json' with { type: 'json' }; +import { publishBrowserControlNotifications } from './notification-publisher.js'; import { buildBrowserControlPanelView } from './panel-view.js'; /** Hosts may augment their RPC catalogue without exporting those declarations to CDB. */ interface PanelContext { + /** Hub hosts supply their shared message feed. Plain view hosts may omit notifications. */ + readonly messages?: BrowserControlMessages; rpc: { sharedState: Pick }; scope: (namespace: string) => { readonly rpc: Pick & { register: (definition: Pick[0], 'name' | 'type' | 'handler'>) => unknown; @@ -33,6 +38,8 @@ export interface CdbPanelOptions { readonly name?: string; /** Match the notification action to the embedding application's approval channel. */ readonly approvalAction?: 'review' | 'accept'; + /** Receives structured host message creation, update, and dismissal failures. */ + readonly onNotificationError?: (error: BrowserControlErrorData) => void; /** A mounted host may use its own JSON renderer and component properties. */ readonly renderer?: { readonly type: string; readonly components?: BrowserControlPanelComponents }; readonly dock?: { readonly category?: string; readonly defaultOrder?: number }; @@ -47,8 +54,22 @@ export interface CdbPanel { /** Serves Devframe's reference SPA; mounted hosts can select their own renderer. */ export function createCdbPanel(options: CdbPanelOptions): CdbPanel { + definePanel(options); const directory = dirname(fileURLToPath(import.meta.url)); + const clientScript = { + eager: true, + importFrom: join(directory, 'view/page-script.js'), + ...(options.approvalAction === 'accept' ? { importName: 'setupBrowserControlAcceptPage' } : {}), + }; let disposed = false; + let stopNotifications: (() => void) | undefined; + const notifications = createBrowserControlNotificationController({ + onReview() { + throw new Error('Browser approval must run in the viewing tab.'); + }, + onReject: async requestId => activeClient().revokeScope(requestId), + onRevoke: async requestId => activeClient().revokeScope(requestId), + }); let unsubscribe: (() => void) | undefined; let view: JsonRenderView | undefined; let client: BrowserControlPanelClient | undefined; @@ -74,13 +95,18 @@ export function createCdbPanel(options: CdbPanelOptions): CdbPanel { icon: 'ph:browser-duotone', capabilities: { build: false }, clientAssets: jsonRenderSpaDir, - dock: { defaultOrder: 1_000, ...options.dock, clientScript: { importFrom: join(directory, 'view/page-script.js'), ...(options.approvalAction === 'accept' ? { importName: 'setupBrowserControlAcceptPage' } : {}) } }, + dock: { + defaultOrder: 1_000, + ...options.dock, + clientScript, + }, async setup(context) { if (disposed) throw new Error('The CDB panel was disposed.'); if (!clientResolved) { client = options.client(); clientResolved = true; } + if (context.messages !== undefined) stopNotifications = publishBrowserControlNotifications(notifications, context.messages, options.approvalAction, options.onNotificationError); const renderer = 'docks' in context ? options.renderer : undefined; const rpc = context.scope('cdb:panel').rpc; const initial = emptyState(); @@ -103,6 +129,7 @@ export function createCdbPanel(options: CdbPanelOptions): CdbPanel { value.available = selected !== undefined; }); view?.update(buildBrowserControlPanelView(broker, renderer?.components)); + notifications.update(broker); }; publish(emptyState()); if (selected === undefined) return; @@ -129,6 +156,15 @@ export function createCdbPanel(options: CdbPanelOptions): CdbPanel { const hub = context as unknown as DevframeHubContext; const entry = hub.docks.views.get('cdb-browser-control'); if (entry === undefined) throw new Error('Install the CDB panel before selecting its renderer.'); + const resolvedClientScript = 'clientScript' in entry ? entry.clientScript ?? clientScript : clientScript; + hub.docks.register({ + id: 'cdb-browser-control-page-script', + title: 'Browser control page integration', + icon: 'ph:browser-duotone', + type: 'action', + visibility: 'false', + action: resolvedClientScript, + }); const dock = toJsonRenderDockEntry(view, entry); /** Host renderer names use the same JSON view contract as the reference renderer. */ hub.docks.update({ ...dock, type: (options.renderer?.type ?? 'json-render') as typeof dock.type }); @@ -145,6 +181,9 @@ export function createCdbPanel(options: CdbPanelOptions): CdbPanel { dispose() { disposed = true; generation += 1; + stopNotifications?.(); + stopNotifications = undefined; + notifications.dispose(); unsubscribe?.(); unsubscribe = undefined; view?.dispose(); @@ -152,3 +191,8 @@ export function createCdbPanel(options: CdbPanelOptions): CdbPanel { }, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function definePanel(definition: Definition): Definition { + return definition; +} diff --git a/packages/devframe/src/panel-view.ts b/packages/devframe/src/panel-view.ts index 82ffe21..9ff2682 100644 --- a/packages/devframe/src/panel-view.ts +++ b/packages/devframe/src/panel-view.ts @@ -5,6 +5,27 @@ export interface BrowserControlPanelComponents { readonly [component: string]: { readonly type?: string; readonly props?: Record }; } +function truncateDestination(value: string): string { + const maximumLength = 88; + if (value.length <= maximumLength) return value; + return `${value.slice(0, 64)}...${value.slice(-(maximumLength - 67))}`; +} + +function destinationPresentation(value: string): { readonly label: string; readonly href?: string } { + try { + const destination = new URL(value); + if (destination.protocol === 'http:' || destination.protocol === 'https:') { + return { + label: truncateDestination(`${destination.host}${destination.pathname === '/' ? '' : destination.pathname}`), + href: value, + }; + } + } catch { + return { label: truncateDestination(value) }; + } + return { label: truncateDestination(value) }; +} + /** One view for the reference renderer and host-supplied component registries. */ export function buildBrowserControlPanelView(state: BrokerState, components: BrowserControlPanelComponents = {}): Spec { const elements: Record = {}; @@ -13,28 +34,41 @@ export function buildBrowserControlPanelView(state: BrokerState, components: Bro elements[id] = { type: override?.type ?? type, props: { ...props, ...override?.props }, ...(children === undefined ? {} : { children }) }; return id; } - function stack(id: string, children: string[], row = false): string { - return element(id, 'Stack', { direction: row ? 'row' : 'column', gap: 12, ...(row ? { wrap: true, align: 'stretch' } : {}) }, children); + function stack(id: string, children: string[], row = false, justify?: 'start' | 'end' | 'between'): string { + return element(id, 'Stack', { direction: row ? 'row' : 'column', gap: 12, ...(row ? { wrap: true, align: 'stretch' } : {}), ...(justify === undefined ? {} : { justify }) }, children); } function text(id: string, value: string, variant = 'body'): string { return element(id, 'Text', { text: value, variant }); } - function button(id: string, label: string, action: string, parameters: Record, danger = false): string { + function actionButton(id: string, label: string, action: string, parameters: Record, danger = false): string { element(id, 'Button', { label, variant: danger ? 'danger' : 'secondary' }); elements[id]!.on = { press: { action: `cdb:panel:${action}`, params: parameters } }; - return stack(`${id}-actions`, [id], true); + return id; } function facts(id: string, data: Record): string { return element(id, 'KeyValueTable', { data }); } - function details(id: string, data: Record, actions: string[] = []): string { - return element(id, 'Card', { title: 'Connection details', collapsible: true, defaultCollapsed: true }, [stack(`${id}-body`, [facts(`${id}-facts`, data), ...actions])]); + function destination(id: string, value: string): string { + const presentation = destinationPresentation(value); + return element(id, 'Stack', { direction: 'column', gap: 4 }, [ + text(`${id}-label`, 'Destination', 'caption'), + presentation.href === undefined + ? text(`${id}-value`, presentation.label, 'code') + : element(`${id}-link`, 'Link', { href: presentation.href, label: presentation.label, external: true }), + ]); } function card(id: string, title: string, children: string[]): string { return element(id, 'Card', { title }, [stack(`${id}-body`, children)]); } + function untitledCard(id: string, children: string[]): string { + return element(id, 'Card', {}, [stack(`${id}-body`, children)]); + } + function responsiveCell(child: string): string { + return element(`${child}-cell`, 'Stack', { flex: '0 1 480px' }, [child]); + } function section(id: string, children: string[], empty: string): string { - return stack(id, children.length === 0 ? [text(`${id}-empty`, empty)] : children); + if (children.length === 0) return stack(id, [text(`${id}-empty`, empty)]); + return stack(id, children.map(responsiveCell), true); } const summaries = [ ['providers', 'Connected browsers', state.providers.filter(provider => provider.state === 'ready').length], @@ -52,20 +86,26 @@ export function buildBrowserControlPanelView(state: BrokerState, components: Bro const target = state.targets.find(candidate => candidate.id === grant.targetId); const badge = element(`${id}-status`, 'Badge', { text: grant.state, variant: grant.state === 'active' ? 'success' : 'warning' }); return card(id, target?.title ?? 'Approved tab', [ - stack(`${id}-header`, [text(`${id}-client`, grant.principalLabel), badge], true), + stack(`${id}-header`, [text(`${id}-client`, grant.principalLabel), badge], true, 'between'), + ...(target?.url === undefined ? [] : [destination(`${id}-destination`, target.url)]), facts(`${id}-access`, { Grant: grant.level, Navigation: grant.navigation, Origin: grant.approvedOrigin }), - ...(target?.url === undefined ? [] : [text(`${id}-url`, target.url, 'caption')]), - button(`${id}-revoke`, 'Revoke target', 'revoke-target', { grantId: grant.id }, true), - details(`${id}-details`, { 'Grant ID': grant.id, 'Target ID': grant.targetId, 'Provider': grant.providerId }), + element(`${id}-connection-divider`, 'Divider', { label: 'Connection' }), + facts(`${id}-connection`, { 'Grant ID': grant.id, 'Target ID': grant.targetId, 'Provider': grant.providerId }), + stack(`${id}-footer`, [actionButton(`${id}-revoke`, 'Revoke target', 'revoke-target', { grantId: grant.id }, true)], true, 'end'), ]); }); const providers = state.providers.map((provider) => { const id = `provider-${provider.id}`; - return card(id, provider.name, [ - element(`${id}-status`, 'Badge', { text: provider.state, variant: provider.state === 'ready' ? 'success' : 'warning' }), + const status = element(`${id}-status`, 'Badge', { text: provider.state, variant: provider.state === 'ready' ? 'success' : 'warning' }); + return untitledCard(id, [ + stack(`${id}-header`, [text(`${id}-name`, provider.name, 'subheading'), status], true, 'between'), facts(`${id}-facts`, { Tabs: provider.targetCount, Version: provider.version, Pairing: provider.paired ? 'Paired' : 'Not paired' }), - button(`${id}-disconnect`, 'Disconnect', 'disconnect', { providerId: provider.id }), - details(`${id}-details`, { 'Provider ID': provider.id, 'Installation ID': provider.instanceId, 'Maximum access': provider.maximumLevel }, [button(`${id}-forget`, 'Forget pairing', 'forget', { providerId: provider.id }, true)]), + element(`${id}-connection-divider`, 'Divider', { label: 'Connection' }), + facts(`${id}-connection`, { 'Provider ID': provider.id, 'Installation ID': provider.instanceId, 'Maximum access': provider.maximumLevel }), + stack(`${id}-footer`, [ + actionButton(`${id}-disconnect`, 'Disconnect', 'disconnect', { providerId: provider.id }), + actionButton(`${id}-forget`, 'Forget pairing', 'forget', { providerId: provider.id }, true), + ], true, 'end'), ]); }); const operations = state.leases.map((lease, index) => card(`operation-${index}`, state.principals.find(principal => principal.id === lease.principalId)?.label ?? 'Browser client', [facts(`operation-${index}-facts`, { Mode: lease.mode, Methods: lease.methods.join(', ') })])); diff --git a/packages/devframe/src/panel.ts b/packages/devframe/src/panel.ts index 05530ed..b32fdc3 100644 --- a/packages/devframe/src/panel.ts +++ b/packages/devframe/src/panel.ts @@ -24,6 +24,7 @@ export interface BrowserControlPanelOptions { /** Mounts framework-neutral management UI into an existing application. */ export async function mountBrowserControlPanel(options: BrowserControlPanelOptions): Promise<{ dispose: () => void }> { + definePanelMount(options); const document = options.container.ownerDocument; const host = document.createElement('section'); const root = host.attachShadow({ mode: 'open' }); @@ -44,7 +45,11 @@ export async function mountBrowserControlPanel(options: BrowserControlPanelOptio root.append(style, heading, notifications, content); options.container.append(host); let disposed = false; - const controller = createBrowserControlNotificationController({ onReview: options.onReview, onRevoke: async requestId => options.client.revokeScope(requestId) }); + const controller = createBrowserControlNotificationController({ + onReview: options.onReview, + onReject: async requestId => options.client.revokeScope(requestId), + onRevoke: async requestId => options.client.revokeScope(requestId), + }); const renderer = renderBrowserControlNotifications({ controller, container: notifications, ...(options.branding === undefined ? {} : { branding: options.branding }) }); function text(parent: HTMLElement, tag: string, value: string): void { @@ -137,3 +142,8 @@ export function createBrowserControlPanelClient(client: CdbDevframeClient): Brow disconnectProvider: async (providerId, forgetPairing = false) => rpc.call('disconnect-provider', providerId, forgetPairing) as Promise, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function definePanelMount(definition: Definition): Definition { + return definition; +} diff --git a/packages/devframe/src/service.ts b/packages/devframe/src/service.ts index 8b6555f..d79d59b 100644 --- a/packages/devframe/src/service.ts +++ b/packages/devframe/src/service.ts @@ -6,7 +6,7 @@ import type { DevframeNodeRpcSession, DevframeRpcConnection, DevframeScopedNodeC import { Buffer } from 'node:buffer'; import { agentToBrokerMessageSchema } from '@dvcol/cdb'; -import { BrokerError, createBroker } from '@dvcol/cdb-broker'; +import { BrokerError, createBroker, defineBroker } from '@dvcol/cdb-broker'; import { normalizeBrowserControlError } from '@dvcol/cdb-broker/contract'; import packageManifest from '../package.json' with { type: 'json' }; @@ -60,6 +60,7 @@ declare module 'devframe/types' { /** Install once for the Devframe context. The host forwards peer lifecycle and explicitly disposes it. */ export function createCdbService(options: CdbServiceOptions = {}): DevframeServiceDefinition { + defineService(options); return { package: '@dvcol/cdb-devframe', version: packageManifest.version, @@ -247,3 +248,9 @@ function installBroker(context: DevframeScopedNodeContext, broker: BrokerRuntime }, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineService(definition: Definition): Definition { + defineBroker(definition.broker ?? {}); + return definition; +} diff --git a/packages/devframe/test/notification-ownership.test.ts b/packages/devframe/test/notification-ownership.test.ts new file mode 100644 index 0000000..ea03624 --- /dev/null +++ b/packages/devframe/test/notification-ownership.test.ts @@ -0,0 +1,86 @@ +// @vitest-environment jsdom +import type { BrokerState } from '@dvcol/cdb-broker/contract'; + +import type { BrowserControlMessages } from '../src/notification-items.js'; +import type { BrowserControlApprovalEventDetail, BrowserControlPageContext } from '../src/page-script.js'; +import type { BrowserControlPanelClient } from '../src/panel.js'; + +import { expect, it, vi } from 'vitest'; + +import { browserControlAcceptEvent, setupBrowserControlAcceptPage } from '../src/page-script.js'; +import { createCdbPanel } from '../src/panel-definition.js'; +import * as panel from '../src/panel.js'; + +vi.mock('@devframes/json-render/node', () => ({ createJsonRenderView: () => ({ update: vi.fn(), dispose: vi.fn() }) })); + +it('publishes one shared notification with distinct accept/reject actions in both clients and keeps it when either client leaves', async () => { + expect.assertions(11); + const state: BrokerState = { revision: 1, providers: [], principals: [], targets: [], grants: [], scopes: [], leases: [], requests: [{ id: 'request', principalId: 'principal', principalLabel: 'Agent', level: 'interact', navigation: 'same-origin', state: 'pending', createdAt: 0, expiresAt: null }] }; + const listeners = new Set<(state: BrokerState) => void>(); + const revokeScope = vi.fn(async () => {}); + const client: BrowserControlPanelClient = { + snapshot: () => state, + watch(listener) { + listeners.add(listener); + listener(state); + return () => listeners.delete(listener); + }, + revokeScope, + revokeGrant: async () => true, + disconnectProvider: async () => true, + }; + const mock = vi.spyOn(panel, 'createBrowserControlPanelClient').mockReturnValue(client); + const dismiss = vi.fn(async () => {}); + const info = vi.fn(async () => ({ dismiss, update: vi.fn(async () => {}) })); + const messages = { info }; + const host = createCdbPanel({ client: () => client, approvalAction: 'accept' }); + const context = { + messages, + rpc: { sharedState: { get: vi.fn() } }, + scope: () => ({ rpc: { + register: vi.fn(), + sharedState: async (_key: string, options: { initialValue: unknown }) => ({ mutate: (mutate: (value: unknown) => void) => mutate(options.initialValue) }), + } }), + } as unknown as Parameters[0]; + const firstUnregister = vi.fn(); + const secondUnregister = vi.fn(); + const firstRegister = vi.fn(() => firstUnregister); + const secondRegister = vi.fn(() => secondUnregister); + const dispatch = vi.spyOn(window, 'dispatchEvent'); + const approval = (event: Event): void => { + (event as CustomEvent).detail.respondWith(Promise.resolve()); + }; + window.addEventListener(browserControlAcceptEvent, approval); + await host.definition.setup(context); + const common = { rpc: {} as BrowserControlPageContext['rpc'] }; + const disposeFirst = await setupBrowserControlAcceptPage({ ...common, commands: { register: firstRegister } }); + const disposeSecond = await setupBrowserControlAcceptPage({ ...common, commands: { register: secondRegister } }); + try { + expect(info).toHaveBeenCalledOnce(); + const [acceptCommand, rejectCommand] = info.mock.calls[0]![1].actions; + expect(firstRegister.mock.calls.map(call => call[0].id)).toEqual([acceptCommand!.command.id, rejectCommand!.command.id]); + expect(secondRegister.mock.calls.map(call => call[0].id)).toEqual([acceptCommand!.command.id, rejectCommand!.command.id]); + disposeFirst(); + await Promise.resolve(); + expect(firstUnregister).toHaveBeenCalledTimes(2); + expect(secondUnregister).not.toHaveBeenCalled(); + expect(dismiss).not.toHaveBeenCalled(); + await secondRegister.mock.calls[0]![0].action(); + const dispatched = dispatch.mock.calls[0]![0] as CustomEvent; + expect({ type: dispatched.type, requestId: dispatched.detail.requestId }).toEqual({ type: browserControlAcceptEvent, requestId: 'request' }); + await secondRegister.mock.calls[1]![0].action(); + expect(revokeScope).toHaveBeenCalledWith('request'); + for (const listener of listeners) listener({ ...state, requests: [] }); + await vi.waitUntil(() => dismiss.mock.calls.length === 1); + expect(secondUnregister).toHaveBeenCalledTimes(2); + expect(dismiss).toHaveBeenCalledOnce(); + await expect(secondRegister.mock.calls[1]![0].action()).rejects.toThrow('no longer pending'); + } finally { + disposeFirst(); + disposeSecond(); + host.dispose(); + window.removeEventListener(browserControlAcceptEvent, approval); + mock.mockRestore(); + dispatch.mockRestore(); + } +}); diff --git a/packages/devframe/test/notification-publisher.test.ts b/packages/devframe/test/notification-publisher.test.ts new file mode 100644 index 0000000..2f9f5ea --- /dev/null +++ b/packages/devframe/test/notification-publisher.test.ts @@ -0,0 +1,154 @@ +import type { BrokerState } from '@dvcol/cdb-broker/contract'; + +import { createBrowserControlNotificationController } from '@dvcol/cdb-extension/notifications'; +import { expect, it, vi } from 'vitest'; + +import { publishBrowserControlNotifications } from '../src/notification-publisher.js'; + +async function notificationFixture(message = { dismiss: vi.fn(async () => {}), update: vi.fn(async (_patch: { description: string }) => {}) }, creation?: Promise) { + const state: BrokerState = { revision: 1, providers: [], principals: [], targets: [], grants: [], scopes: [], leases: [], requests: [{ id: 'request', principalId: 'principal', principalLabel: 'Agent', level: 'interact', navigation: 'same-origin', state: 'pending', createdAt: 0, expiresAt: null }] }; + const controller = createBrowserControlNotificationController({ onReview: vi.fn(), onReject: vi.fn(async () => {}), onRevoke: vi.fn(async () => {}) }); + const info = vi.fn(async () => creation ?? message); + const stopPublishing = publishBrowserControlNotifications(controller, { info }); + controller.update(state); + await Promise.resolve(); + return { + state, + publish: (next: BrokerState) => controller.update(next), + message, + info, + dispose() { + stopPublishing(); + controller.dispose(); + }, + }; +} + +it('updates changed descriptions through the existing message without registering another command', async () => { + expect.assertions(3); + const fixture = await notificationFixture(); + try { + fixture.publish({ ...fixture.state, revision: 2 }); + expect(fixture.message.update).not.toHaveBeenCalled(); + fixture.publish({ ...fixture.state, requests: [{ ...fixture.state.requests[0]!, principalLabel: 'Renamed agent' }] }); + await vi.waitUntil(() => fixture.message.update.mock.calls.length === 1); + expect(fixture.message.update).toHaveBeenCalledWith({ description: 'Renamed agent requests interact access with same-origin navigation.' }); + expect(fixture.info).toHaveBeenCalledOnce(); + } finally { + fixture.dispose(); + } +}); + +it('updates overlapping-grant tab counts without notifying unchanged publications', async () => { + expect.assertions(3); + const fixture = await notificationFixture(); + const grant = { id: 'grant', requestId: 'request', principalId: 'principal', principalLabel: 'Agent', providerId: 'provider', targetId: 'one', targetGeneration: 1, level: 'interact' as const, navigation: 'same-origin' as const, approvedOrigin: 'https://example.test', createdAt: 0, state: 'active' as const }; + try { + fixture.publish({ ...fixture.state, requests: [], grants: [grant] }); + fixture.publish({ ...fixture.state, requests: [], grants: [grant, { ...grant, id: 'second', targetId: 'two' }] }); + await vi.waitUntil(() => fixture.message.update.mock.calls.length === 1); + expect(fixture.message.update).toHaveBeenLastCalledWith({ description: 'Agent: interact access to 2 approved tabs.' }); + fixture.publish({ ...fixture.state, revision: 3, requests: [], grants: [grant, { ...grant, id: 'second', targetId: 'two' }] }); + await Promise.resolve(); + expect(fixture.message.update).toHaveBeenCalledOnce(); + expect(fixture.info).toHaveBeenCalledTimes(2); + } finally { + fixture.dispose(); + } +}); + +it('serializes updates and dismisses after a pending update without publishing queued stale data', async () => { + expect.assertions(4); + const pending = Promise.withResolvers(); + const update = vi.fn(async (_patch: { description: string }) => pending.promise); + const fixture = await notificationFixture({ update, dismiss: vi.fn(async () => {}) }); + try { + fixture.publish({ ...fixture.state, requests: [{ ...fixture.state.requests[0]!, principalLabel: 'First' }] }); + await vi.waitUntil(() => update.mock.calls.length === 1); + fixture.publish({ ...fixture.state, requests: [{ ...fixture.state.requests[0]!, principalLabel: 'Second' }] }); + await Promise.resolve(); + expect(update).toHaveBeenCalledOnce(); + fixture.publish({ ...fixture.state, requests: [] }); + expect(fixture.message.dismiss).not.toHaveBeenCalled(); + pending.resolve(); + await vi.waitUntil(() => fixture.message.dismiss.mock.calls.length === 1); + expect(update).toHaveBeenCalledOnce(); + expect(fixture.message.dismiss).toHaveBeenCalledOnce(); + } finally { + pending.resolve(); + fixture.dispose(); + } +}); + +it.each(['expiry', 'disposal'] as const)('cleans up delayed message creation after %s', async (ending) => { + expect.assertions(2); + const message = { dismiss: vi.fn(async () => {}), update: vi.fn(async (_patch: { description: string }) => {}) }; + const creation = Promise.withResolvers(); + const fixture = await notificationFixture(message, creation.promise); + try { + if (ending === 'expiry') fixture.publish({ ...fixture.state, requests: [{ ...fixture.state.requests[0]!, expiresAt: 1 }] }); + else fixture.dispose(); + creation.resolve(message); + await vi.waitUntil(() => message.dismiss.mock.calls.length === 1); + expect(message.dismiss).toHaveBeenCalledOnce(); + expect(message.update).not.toHaveBeenCalled(); + } finally { + fixture.dispose(); + } +}); + +it('reports structured update failures, continues updates, and still removes the message', async () => { + expect.assertions(3); + const state: BrokerState = { revision: 1, providers: [], principals: [], targets: [], grants: [], scopes: [], leases: [], requests: [{ id: 'request', principalId: 'principal', principalLabel: 'Agent', level: 'interact', navigation: 'same-origin', state: 'pending', createdAt: 0, expiresAt: null }] }; + const message = { dismiss: vi.fn(async () => {}), update: vi.fn(async (_patch: { description: string }) => {}) }; + const failure = Object.assign(new Error('Host update failed'), { code: 'MESSAGE_UPDATE_FAILED', retryable: true, retryAfterMilliseconds: 100 }); + const report = vi.fn(); + const controller = createBrowserControlNotificationController({ onReview: vi.fn(), onReject: vi.fn(async () => {}), onRevoke: vi.fn(async () => {}) }); + const info = vi.fn(async () => message); + const stopPublishing = publishBrowserControlNotifications(controller, { info }, 'review', report); + controller.update(state); + await Promise.resolve(); + message.update.mockRejectedValueOnce(failure); + try { + controller.update({ ...state, requests: [{ ...state.requests[0]!, principalLabel: 'First' }] }); + await vi.waitUntil(() => report.mock.calls.length === 1); + controller.update({ ...state, requests: [{ ...state.requests[0]!, principalLabel: 'Second' }] }); + await vi.waitUntil(() => message.update.mock.calls.length === 2); + expect(message.update).toHaveBeenLastCalledWith({ description: 'Second requests interact access with same-origin navigation.' }); + controller.update({ ...state, requests: [] }); + await vi.waitUntil(() => message.dismiss.mock.calls.length === 1); + expect(message.dismiss).toHaveBeenCalledOnce(); + expect(report).toHaveBeenCalledWith({ code: 'MESSAGE_UPDATE_FAILED', message: 'Host update failed', retryable: true, retryAfterMilliseconds: 100 }); + } finally { + stopPublishing(); + controller.dispose(); + } +}); + +it('finishes old updates and removal before recreating the same message after reconnect', async () => { + expect.assertions(5); + const pending = Promise.withResolvers(); + const update = vi.fn(async (_patch: { description: string }) => pending.promise); + const fixture = await notificationFixture({ update, dismiss: vi.fn(async () => {}) }); + const replacement = { dismiss: vi.fn(async () => {}), update: vi.fn(async () => {}) }; + try { + fixture.publish({ ...fixture.state, requests: [{ ...fixture.state.requests[0]!, principalLabel: 'Changed' }] }); + await vi.waitUntil(() => update.mock.calls.length === 1); + fixture.publish({ ...fixture.state, requests: [] }); + fixture.info.mockResolvedValueOnce(replacement); + fixture.publish(fixture.state); + await Promise.resolve(); + expect(fixture.info).toHaveBeenCalledOnce(); + pending.resolve(); + await vi.waitUntil(() => fixture.info.mock.calls.length === 2); + expect(fixture.message.dismiss).toHaveBeenCalledOnce(); + expect(fixture.message.dismiss.mock.invocationCallOrder[0]).toBeLessThan(fixture.info.mock.invocationCallOrder[1]!); + expect(replacement.dismiss).not.toHaveBeenCalled(); + fixture.dispose(); + await vi.waitUntil(() => replacement.dismiss.mock.calls.length === 1); + expect(replacement.dismiss).toHaveBeenCalledOnce(); + } finally { + pending.resolve(); + fixture.dispose(); + } +}); diff --git a/packages/devframe/test/page-script.test.ts b/packages/devframe/test/page-script.test.ts index 9209d6b..5f5a2d4 100644 --- a/packages/devframe/test/page-script.test.ts +++ b/packages/devframe/test/page-script.test.ts @@ -1,23 +1,26 @@ // @vitest-environment jsdom import type { BrokerState } from '@dvcol/cdb-broker/contract'; -import type { BrowserControlPageContext } from '../src/page-script.js'; +import type { BrowserControlApprovalEventDetail, BrowserControlPageContext } from '../src/page-script.js'; import { expect, it, vi } from 'vitest'; -import setupBrowserControlPage, { setupBrowserControlAcceptPage } from '../src/page-script.js'; +import setupBrowserControlPage, { browserControlAcceptEvent, browserControlReviewEvent, setupBrowserControlAcceptPage } from '../src/page-script.js'; import * as panel from '../src/panel.js'; -it.each(['review', 'accept'] as const)('uses native hub notifications with %s and removes ended requests', async (approvalAction) => { - expect.assertions(9); +it.each(['review', 'accept'] as const)('registers local %s and reject commands', async (approvalAction) => { + expect.assertions(10); const state: BrokerState = { revision: 1, providers: [], principals: [], targets: [], grants: [], scopes: [], leases: [], requests: [{ id: 'request', principalId: 'principal', principalLabel: 'Agent', level: 'interact', navigation: 'same-origin', state: 'pending', createdAt: 0, expiresAt: null }] }; let update: (state: BrokerState) => void = () => {}; const stopWatching = vi.fn(); - const dismiss = vi.fn(async () => {}); const unregister = vi.fn(); const register = vi.fn(() => unregister); - const info = vi.fn(async () => ({ dismiss, update: vi.fn(async () => {}) })); const dispatch = vi.spyOn(window, 'dispatchEvent'); + const approval = (event: Event): void => { + (event as CustomEvent).detail.respondWith(Promise.resolve()); + }; + window.addEventListener(approvalAction === 'accept' ? browserControlAcceptEvent : browserControlReviewEvent, approval); + const revokeScope = vi.fn(async () => {}); const client = vi.spyOn(panel, 'createBrowserControlPanelClient').mockReturnValue({ snapshot: () => state, watch: (listener) => { @@ -25,26 +28,33 @@ it.each(['review', 'accept'] as const)('uses native hub notifications with %s an listener(state); return stopWatching; }, - revokeScope: async () => {}, + revokeScope, revokeGrant: async () => true, disconnectProvider: async () => true, }); const setupPage = approvalAction === 'accept' ? setupBrowserControlAcceptPage : setupBrowserControlPage; - const dispose = await setupPage({ rpc: {} as BrowserControlPageContext['rpc'], current: { domElements: {} }, commands: { register }, messages: { info } }); + const dispose = await setupPage({ rpc: {} as BrowserControlPageContext['rpc'], commands: { register } }); try { - expect(info).toHaveBeenCalledWith('Browser control requested', expect.objectContaining({ notify: true, autoDismiss: false, description: 'Agent requests interact access with same-origin navigation.' })); expect(document.querySelector('aside')).toBeNull(); expect(document.documentElement.hasAttribute('data-cdb-notifications-ready')).toBe(true); - const command = register.mock.calls[0]![0]; - expect(command.title).toBe(approvalAction === 'accept' ? 'Accept' : 'Review request'); - await command.action(); - expect(dispatch).toHaveBeenCalledWith(expect.objectContaining({ type: `cdb:${approvalAction}-request`, detail: { requestId: 'request' } })); + const [acceptCommand, rejectCommand] = register.mock.calls.map(call => call[0]); + expect(acceptCommand!.id).toBe(`cdb:browser-control:request:request:${approvalAction}`); + expect(acceptCommand!.title).toBe(approvalAction === 'accept' ? 'Accept' : 'Review request'); + expect(rejectCommand!.title).toBe('Reject'); + await acceptCommand!.action(); + const dispatched = dispatch.mock.calls[0]![0] as CustomEvent; + expect({ + type: dispatched.type, + requestId: dispatched.detail.requestId, + respondWith: typeof dispatched.detail.respondWith, + }).toEqual({ type: `cdb:${approvalAction}-request`, requestId: 'request', respondWith: 'function' }); + await rejectCommand!.action(); + expect(revokeScope).toHaveBeenCalledWith('request'); update({ ...state, requests: [] }); - await vi.waitUntil(() => dismiss.mock.calls.length === 1); - expect(unregister).toHaveBeenCalledOnce(); - expect(dismiss).toHaveBeenCalledOnce(); + expect(unregister).toHaveBeenCalledTimes(2); } finally { dispose(); + window.removeEventListener(approvalAction === 'accept' ? browserControlAcceptEvent : browserControlReviewEvent, approval); client.mockRestore(); dispatch.mockRestore(); } @@ -52,123 +62,83 @@ it.each(['review', 'accept'] as const)('uses native hub notifications with %s an expect(document.documentElement.hasAttribute('data-cdb-notifications-ready')).toBe(false); }); -async function notificationFixture(message = { dismiss: vi.fn(async () => {}), update: vi.fn(async (_patch: { description: string }) => {}) }, creation?: Promise) { +it('returns structured approval failures to the notification command', async () => { + expect.assertions(2); const state: BrokerState = { revision: 1, providers: [], principals: [], targets: [], grants: [], scopes: [], leases: [], requests: [{ id: 'request', principalId: 'principal', principalLabel: 'Agent', level: 'interact', navigation: 'same-origin', state: 'pending', createdAt: 0, expiresAt: null }] }; - let publish: (state: BrokerState) => void = () => {}; - const client = vi.spyOn(panel, 'createBrowserControlPanelClient').mockReturnValue({ snapshot: () => state, watch: (listener) => { - publish = listener; - listener(state); - return () => {}; - }, revokeScope: async () => {}, revokeGrant: async () => true, disconnectProvider: async () => true }); - const unregister = vi.fn(); - const register = vi.fn(() => unregister); - const info = vi.fn(async () => creation ?? message); - const dispose = await setupBrowserControlPage({ rpc: {} as BrowserControlPageContext['rpc'], current: { domElements: {} }, commands: { register }, messages: { info } }); - return { state, publish: (next: BrokerState) => publish(next), message, info, register, unregister, dispose: () => { - dispose(); - client.mockRestore(); - } }; -} - -it('updates changed descriptions through the existing message without registering another command', async () => { - expect.assertions(4); - const fixture = await notificationFixture(); - try { - fixture.publish({ ...fixture.state, revision: 2 }); - expect(fixture.message.update).not.toHaveBeenCalled(); - fixture.publish({ ...fixture.state, requests: [{ ...fixture.state.requests[0]!, principalLabel: 'Renamed agent' }] }); - await vi.waitUntil(() => fixture.message.update.mock.calls.length === 1); - expect(fixture.message.update).toHaveBeenCalledWith({ description: 'Renamed agent requests interact access with same-origin navigation.' }); - expect(fixture.info).toHaveBeenCalledOnce(); - expect(fixture.register).toHaveBeenCalledOnce(); - } finally { - fixture.dispose(); - } -}); - -it('updates overlapping-grant tab counts without notifying unchanged publications', async () => { - expect.assertions(3); - const fixture = await notificationFixture(); - const grant = { id: 'grant', requestId: 'request', principalId: 'principal', principalLabel: 'Agent', providerId: 'provider', targetId: 'one', targetGeneration: 1, level: 'interact' as const, navigation: 'same-origin' as const, approvedOrigin: 'https://example.test', createdAt: 0, state: 'active' as const }; - try { - fixture.publish({ ...fixture.state, requests: [], grants: [grant] }); - fixture.publish({ ...fixture.state, requests: [], grants: [grant, { ...grant, id: 'second', targetId: 'two' }] }); - await vi.waitUntil(() => fixture.message.update.mock.calls.length === 1); - expect(fixture.message.update).toHaveBeenLastCalledWith({ description: 'Agent: interact access to 2 approved tabs.' }); - fixture.publish({ ...fixture.state, revision: 3, requests: [], grants: [grant, { ...grant, id: 'second', targetId: 'two' }] }); - await Promise.resolve(); - expect(fixture.message.update).toHaveBeenCalledOnce(); - expect(fixture.info).toHaveBeenCalledTimes(2); - } finally { - fixture.dispose(); - } -}); - -it('serializes updates and dismisses after a pending update without publishing queued stale data', async () => { - expect.assertions(5); - const pending = Promise.withResolvers(); - const update = vi.fn(async (_patch: { description: string }) => pending.promise); - const fixture = await notificationFixture({ update, dismiss: vi.fn(async () => {}) }); - try { - fixture.publish({ ...fixture.state, requests: [{ ...fixture.state.requests[0]!, principalLabel: 'First' }] }); - await vi.waitUntil(() => update.mock.calls.length === 1); - fixture.publish({ ...fixture.state, requests: [{ ...fixture.state.requests[0]!, principalLabel: 'Second' }] }); - await Promise.resolve(); - expect(update).toHaveBeenCalledOnce(); - fixture.publish({ ...fixture.state, requests: [] }); - expect(fixture.unregister).toHaveBeenCalledOnce(); - expect(fixture.message.dismiss).not.toHaveBeenCalled(); - pending.resolve(); - await vi.waitUntil(() => fixture.message.dismiss.mock.calls.length === 1); - expect(update).toHaveBeenCalledOnce(); - expect(fixture.message.dismiss).toHaveBeenCalledOnce(); - } finally { - pending.resolve(); - fixture.dispose(); - } -}); - -it.each(['expiry', 'disposal'] as const)('cleans up delayed message creation after %s', async (ending) => { - expect.assertions(3); - const message = { dismiss: vi.fn(async () => {}), update: vi.fn(async (_patch: { description: string }) => {}) }; - const creation = Promise.withResolvers(); - const fixture = await notificationFixture(message, creation.promise); + let command: Parameters[0] | undefined; + const mock = vi.spyOn(panel, 'createBrowserControlPanelClient').mockReturnValue({ + snapshot: () => state, + watch(listener) { + listener(state); + return () => {}; + }, + revokeScope: async () => {}, + revokeGrant: async () => true, + disconnectProvider: async () => true, + }); + const failure = Object.assign(new Error('The selected tab disappeared.'), { + code: 'TARGET_GONE', + retryable: true, + retryAfterMilliseconds: 250, + details: { targetId: 'tab-1' }, + }); + const respond = (event: Event): void => { + (event as CustomEvent).detail.respondWith(Promise.reject(failure)); + }; + window.addEventListener(browserControlAcceptEvent, respond); + const dispose = await setupBrowserControlAcceptPage({ + rpc: {} as BrowserControlPageContext['rpc'], + commands: { register(value) { + command ??= value; + return () => {}; + } }, + }); try { - if (ending === 'expiry') fixture.publish({ ...fixture.state, requests: [{ ...fixture.state.requests[0]!, expiresAt: 1 }] }); - else fixture.dispose(); - expect(fixture.unregister).toHaveBeenCalledOnce(); - creation.resolve(message); - await vi.waitUntil(() => message.dismiss.mock.calls.length === 1); - expect(message.dismiss).toHaveBeenCalledOnce(); - expect(message.update).not.toHaveBeenCalled(); + await expect(command!.action()).rejects.toMatchObject({ + code: 'TARGET_GONE', + message: 'The selected tab disappeared.', + retryable: true, + retryAfterMilliseconds: 250, + details: { targetId: 'tab-1' }, + }); + expect(command!.id).toBe('cdb:browser-control:request:request:accept'); } finally { - fixture.dispose(); + dispose(); + window.removeEventListener(browserControlAcceptEvent, respond); + mock.mockRestore(); } }); -it('continues message updates after a rejected update and still removes the message', async () => { - expect.assertions(3); - const fixture = await notificationFixture(); - const failure = new Error('Host update failed'); - fixture.message.update.mockRejectedValueOnce(failure); - const report = vi.spyOn(console, 'error').mockImplementation(() => {}); +it('fails approval commands when no host handler responds', async () => { + expect.assertions(1); + const state: BrokerState = { revision: 1, providers: [], principals: [], targets: [], grants: [], scopes: [], leases: [], requests: [{ id: 'request', principalId: 'principal', principalLabel: 'Agent', level: 'interact', navigation: 'same-origin', state: 'pending', createdAt: 0, expiresAt: null }] }; + let command: Parameters[0] | undefined; + const mock = vi.spyOn(panel, 'createBrowserControlPanelClient').mockReturnValue({ + snapshot: () => state, + watch(listener) { + listener(state); + return () => {}; + }, + revokeScope: async () => {}, + revokeGrant: async () => true, + disconnectProvider: async () => true, + }); + const dispose = await setupBrowserControlPage({ + rpc: {} as BrowserControlPageContext['rpc'], + commands: { register(value) { + command ??= value; + return () => {}; + } }, + }); try { - fixture.publish({ ...fixture.state, requests: [{ ...fixture.state.requests[0]!, principalLabel: 'First' }] }); - await vi.waitUntil(() => report.mock.calls.length === 1); - fixture.publish({ ...fixture.state, requests: [{ ...fixture.state.requests[0]!, principalLabel: 'Second' }] }); - await vi.waitUntil(() => fixture.message.update.mock.calls.length === 2); - expect(fixture.message.update).toHaveBeenLastCalledWith({ description: 'Second requests interact access with same-origin navigation.' }); - fixture.publish({ ...fixture.state, requests: [] }); - await vi.waitUntil(() => fixture.message.dismiss.mock.calls.length === 1); - expect(fixture.message.dismiss).toHaveBeenCalledOnce(); - expect(report).toHaveBeenCalledWith('Unable to update browser-control notification.', failure); + await expect(command!.action()).rejects.toMatchObject({ code: 'APPROVAL_HANDLER_UNAVAILABLE', retryable: false }); } finally { - fixture.dispose(); - report.mockRestore(); + dispose(); + mock.mockRestore(); } }); -it('installs host approval bindings only while available and removes notifications on disable', async () => { +it('installs host approval bindings only while available and removes local commands on disable', async () => { expect.assertions(8); const state: BrokerState = { revision: 1, providers: [], principals: [], targets: [], grants: [], scopes: [], leases: [], requests: [{ id: 'request', principalId: 'principal', principalLabel: 'Agent', level: 'interact', navigation: 'same-origin', state: 'pending', createdAt: 0, expiresAt: null }] }; let publish: (state: BrokerState, available?: boolean) => void = () => {}; @@ -185,21 +155,19 @@ it('installs host approval bindings only while available and removes notificatio }); const removeBindings = vi.fn(); const onAvailable = vi.fn(() => removeBindings); - const dismiss = vi.fn(async () => {}); - const info = vi.fn(async () => ({ dismiss, update: vi.fn(async () => {}) })); - const dispose = await setupBrowserControlAcceptPage({ rpc: {} as BrowserControlPageContext['rpc'], current: { domElements: {} }, commands: { register: () => () => {} }, messages: { info } }, { onAvailable }); + const unregister = vi.fn(); + const register = vi.fn(() => unregister); + const dispose = await setupBrowserControlAcceptPage({ rpc: {} as BrowserControlPageContext['rpc'], commands: { register } }, { onAvailable }); try { expect(onAvailable).not.toHaveBeenCalled(); - expect(info).not.toHaveBeenCalled(); publish(state, true); publish(state, true); expect(onAvailable).toHaveBeenCalledOnce(); - await vi.waitUntil(() => info.mock.calls.length === 1); + expect(register).toHaveBeenCalledTimes(2); publish(state, false); expect(removeBindings).toHaveBeenCalledOnce(); expect(document.documentElement.hasAttribute('data-cdb-notifications-ready')).toBe(false); - await vi.waitUntil(() => dismiss.mock.calls.length === 1); - expect(dismiss).toHaveBeenCalledOnce(); + expect(unregister).toHaveBeenCalledTimes(2); publish(state, true); expect(onAvailable).toHaveBeenCalledTimes(2); } finally { diff --git a/packages/devframe/test/panel-definition.test.ts b/packages/devframe/test/panel-definition.test.ts index da1611d..9be0789 100644 --- a/packages/devframe/test/panel-definition.test.ts +++ b/packages/devframe/test/panel-definition.test.ts @@ -10,6 +10,17 @@ vi.mock('@devframes/json-render/node', () => ({ createJsonRenderView: () => ({ u const state: BrokerState = { revision: 1, providers: [], principals: [], requests: [], targets: [], grants: [], leases: [], scopes: [] }; +interface FixtureDockEntry { + readonly id: string; + readonly type: string; + readonly visibility?: string; + readonly action?: { + readonly eager?: boolean; + readonly importFrom: string; + readonly importName?: string; + }; +} + function fixtureClient(revision: number) { let publish: (state: BrokerState) => void = () => {}; const unsubscribe = vi.fn(); @@ -26,8 +37,13 @@ function fixtureClient(revision: number) { return { client, unsubscribe, publish: (revision: number) => publish({ ...state, revision }) }; } -function fixtureContext() { +function fixtureContext(options: { hub?: boolean } = {}) { let value = { broker: state }; + const docks = { + views: new Map([['cdb-browser-control', { id: 'cdb-browser-control', title: 'Browser control', icon: 'ph:browser-duotone', type: 'iframe', url: '/' }]]), + register: vi.fn((_entry: FixtureDockEntry) => ({ update: vi.fn() })), + update: vi.fn(), + }; const context = { rpc: { sharedState: { get: vi.fn() } }, scope: () => ({ rpc: { @@ -37,8 +53,9 @@ function fixtureContext() { return { mutate: (mutate: (current: typeof value) => void) => mutate(value) }; }), } }), + ...(options.hub === true ? { docks } : {}), }; - return { context: context as unknown as Parameters['definition']['setup']>[0], current: () => value.broker }; + return { context: context as unknown as Parameters['definition']['setup']>[0], current: () => value.broker, docks }; } it('rebinds mounted presentation and rejects obsolete state after replacement or disabling', async () => { @@ -81,3 +98,25 @@ it('does not install a stale subscription when snapshot completion races with re expect(fixture.current().revision).toBe(2); panel.dispose(); }); + +it('registers the page integration as a hidden eager action alongside the rendered dock', async () => { + expect.assertions(4); + const fixture = fixtureContext({ hub: true }); + const panel = createCdbPanel({ client: () => fixtureClient(1).client }); + await panel.definition.setup(fixture.context); + expect(fixture.docks.register).toHaveBeenCalledOnce(); + const entry = fixture.docks.register.mock.calls[0]?.[0]; + expect(entry).toMatchObject({ id: 'cdb-browser-control-page-script', type: 'action', visibility: 'false', action: { eager: true } }); + expect(entry?.action?.importFrom).toMatch(/view\/page-script\.js$/u); + expect(entry?.action?.importName).toBeUndefined(); + panel.dispose(); +}); + +it('selects the accept-page export for the hidden eager action when approvalAction is "accept"', async () => { + expect.assertions(1); + const fixture = fixtureContext({ hub: true }); + const panel = createCdbPanel({ client: () => fixtureClient(1).client, approvalAction: 'accept' }); + await panel.definition.setup(fixture.context); + expect(fixture.docks.register.mock.calls[0]?.[0]?.action?.importName).toBe('setupBrowserControlAcceptPage'); + panel.dispose(); +}); diff --git a/packages/devframe/test/panel-view.test.ts b/packages/devframe/test/panel-view.test.ts index 1884661..376f221 100644 --- a/packages/devframe/test/panel-view.test.ts +++ b/packages/devframe/test/panel-view.test.ts @@ -8,24 +8,121 @@ const state: BrokerState = { revision: 1, providers: [{ id: 'provider', instanceId: 'installation', name: 'Fixture browser', version: '1.0.0', maximumLevel: 'debug', paired: true, state: 'ready', targetCount: 1 }], principals: [{ id: 'client', label: 'Fixture client', connectedAt: 1, metadata: { privateDetail: 'not-for-display' } }], - requests: [], + requests: [{ + id: 'request', + principalId: 'client', + principalLabel: 'Fixture client', + level: 'interact', + navigation: 'same-origin', + createdAt: 1, + expiresAt: 2, + state: 'pending', + }], grants: [{ id: 'grant', requestId: 'request', principalId: 'client', principalLabel: 'Fixture client', providerId: 'provider', targetId: 'target', targetGeneration: 1, level: 'interact', navigation: 'same-origin', approvedOrigin: 'https://fixture.test', createdAt: 1, state: 'active' }], - targets: [{ id: 'target', generation: 1, scopeId: 'scope', title: 'Fixture page', url: 'https://fixture.test', providerId: 'provider', state: 'available' }], + targets: [{ + id: 'target', + generation: 1, + scopeId: 'scope', + title: 'Fixture page', + url: `https://fixture.test/${'long-path-segment/'.repeat(8)}page?session=private#section`, + providerId: 'provider', + state: 'available', + }], scopes: [], - leases: [], + leases: [{ + id: 'lease', + principalId: 'client', + targetId: 'target', + targetGeneration: 1, + mode: 'shared-read', + methods: ['Page.navigate'], + issuedAt: '2026-09-16T12:00:00.000Z', + expiresAt: '2026-09-16T12:01:00.000Z', + }], }; -it('projects summary counts, tabbed access and exact management actions without private metadata', () => { - expect.assertions(8); +it('projects each access grant as one complete card with a readable destination and footer action', () => { + expect.assertions(19); const view = buildBrowserControlPanelView(state); expect(view.elements['summary-providers-value']?.props).toMatchObject({ text: '1' }); expect(view.elements['summary-access-value']?.props).toMatchObject({ text: '1' }); expect(view.elements.tabs?.children).toEqual(['requests', 'access', 'providers', 'operations']); + expect(view.elements['grant-grant']?.children).toEqual(['grant-grant-body']); + expect(view.elements['grant-grant-body']?.children).toEqual([ + 'grant-grant-header', + 'grant-grant-destination', + 'grant-grant-access', + 'grant-grant-connection-divider', + 'grant-grant-connection', + 'grant-grant-footer', + ]); + expect(view.elements['grant-grant-details']).toBeUndefined(); + expect(view.elements['grant-grant-url']).toBeUndefined(); + expect(view.elements['grant-grant-destination-link']?.props).toMatchObject({ + href: state.targets[0]!.url, + external: true, + }); + const destination = String(view.elements['grant-grant-destination-link']?.props.label); + expect(destination.startsWith('fixture.test/')).toBe(true); + expect(destination).toContain('...'); + expect(destination.endsWith('page')).toBe(true); + expect(destination).not.toContain('session=private'); + expect(view.elements['grant-grant-access']?.props.data).toEqual({ Grant: 'interact', Navigation: 'same-origin', Origin: 'https://fixture.test' }); + expect(view.elements['grant-grant-connection']?.props.data).toEqual({ 'Grant ID': 'grant', 'Target ID': 'target', 'Provider': 'provider' }); + expect(view.elements['grant-grant-footer']?.props).toMatchObject({ direction: 'row', justify: 'end' }); expect(view.elements['grant-grant-revoke']?.on).toEqual({ press: { action: 'cdb:panel:revoke-target', params: { grantId: 'grant' } } }); + expect(view.elements['grant-grant-revoke']?.props).toMatchObject({ label: 'Revoke target', variant: 'danger' }); expect(view.elements['provider-provider-forget']?.on).toEqual({ press: { action: 'cdb:panel:forget', params: { providerId: 'provider' } } }); - expect(view.elements['provider-provider-details']?.props).toMatchObject({ collapsible: true, defaultCollapsed: true }); expect(JSON.stringify(view)).not.toContain('not-for-display'); - expect(view.elements['grant-grant']?.props).toEqual({ title: 'Fixture page' }); +}); + +it('renders provider metadata and actions in one flat card', () => { + expect.assertions(17); + const view = buildBrowserControlPanelView(state); + expect(view.elements['provider-provider']?.props).toEqual({}); + expect(view.elements['provider-provider']?.children).toEqual(['provider-provider-body']); + expect(view.elements['provider-provider-body']?.children).toEqual([ + 'provider-provider-header', + 'provider-provider-facts', + 'provider-provider-connection-divider', + 'provider-provider-connection', + 'provider-provider-footer', + ]); + expect(view.elements['provider-provider-header']?.props).toMatchObject({ direction: 'row', wrap: true, justify: 'between' }); + expect(view.elements['provider-provider-header']?.children).toEqual(['provider-provider-name', 'provider-provider-status']); + expect(view.elements['provider-provider-name']?.props).toEqual({ text: 'Fixture browser', variant: 'subheading' }); + expect(view.elements['provider-provider-status']?.props).toMatchObject({ text: 'ready', variant: 'success' }); + expect(view.elements['provider-provider-body']?.children).not.toContain('provider-provider-status'); + expect(JSON.stringify(view)).toContain('Fixture browser'); + expect(JSON.stringify(view).match(/Fixture browser/gu)).toHaveLength(1); + expect(view.elements['provider-provider-details']).toBeUndefined(); + expect(JSON.stringify(view.elements['provider-provider'])).not.toContain('collapsible'); + expect(view.elements['provider-provider-connection-divider']?.props).toEqual({ label: 'Connection' }); + expect(view.elements['provider-provider-connection']?.props.data).toEqual({ + 'Provider ID': 'provider', + 'Installation ID': 'installation', + 'Maximum access': 'debug', + }); + expect(view.elements['provider-provider-footer']?.props).toMatchObject({ direction: 'row', wrap: true, justify: 'end' }); + expect(view.elements['provider-provider-footer']?.children).toEqual(['provider-provider-disconnect', 'provider-provider-forget']); + expect(view.elements['provider-provider-disconnect']?.props).toMatchObject({ label: 'Disconnect', variant: 'secondary' }); +}); + +it('wraps populated tab card collections into responsive flex cells', () => { + expect.assertions(20); + const view = buildBrowserControlPanelView(state); + for (const [section, card] of [ + ['requests', 'request-request'], + ['access', 'grant-grant'], + ['providers', 'provider-provider'], + ['operations', 'operation-0'], + ] as const) { + expect(view.elements[section]?.props).toMatchObject({ direction: 'row', wrap: true, align: 'stretch' }); + expect(view.elements[section]?.children).toEqual([`${card}-cell`]); + expect(view.elements[`${card}-cell`]?.props).toEqual({ flex: '0 1 480px' }); + expect(view.elements[`${card}-cell`]?.props).not.toEqual({ flex: '1 1 320px' }); + expect(view.elements[`${card}-cell`]?.props).not.toHaveProperty('maxWidth'); + } }); it('lets a host replace component types and styling while retaining content and action bindings', () => { @@ -36,3 +133,13 @@ it('lets a host replace component types and styling while retaining content and expect(view.elements['grant-grant']?.children).toEqual(['grant-grant-body']); expect(view.elements['grant-grant-revoke']?.on).toEqual({ press: { action: 'cdb:panel:revoke-target', params: { grantId: 'grant' } } }); }); + +it('renders non-web destinations as inert text', () => { + expect.assertions(2); + const view = buildBrowserControlPanelView({ + ...state, + targets: [{ ...state.targets[0]!, url: 'chrome://settings/content' }], + }); + expect(view.elements['grant-grant-destination-link']).toBeUndefined(); + expect(view.elements['grant-grant-destination-value']?.props).toEqual({ text: 'chrome://settings/content', variant: 'code' }); +}); diff --git a/packages/extension/README.md b/packages/extension/README.md index f8dd7f1..fd664c2 100644 --- a/packages/extension/README.md +++ b/packages/extension/README.md @@ -20,7 +20,7 @@ Use `provider.approve(requestId, selector, approvalContext)` from the trusted ap The host chooses installation, recovery and pairing storage keys. Preserve those keys when adopting the adapter. Chrome session storage supports publication recovery but is never itself proof of approved authority. -The `@dvcol/cdb-extension/notifications` entry supplies a headless notification controller and an optional neutral shadow-root renderer. Both derive state from the broker; local dismissal never changes authority. Review actions open the host's final approval UI. Branding and CSS are optional renderer inputs. +The `@dvcol/cdb-extension/notifications` entry supplies a headless notification controller and an optional neutral shadow-root renderer. Both derive state from the broker; local dismissal never changes authority. Clicking a pending request card or its Review action opens the host's final approval UI. Accept/Review, Reject, and Dismiss controls do not trigger the card action. Branding and CSS are optional renderer inputs. See the [Devframe example](../../examples/devframe/README.md) for a complete composition over an existing RPC peer. @@ -54,3 +54,13 @@ timeout and disposal. Supply the receiving and target windows, target origin and application message names. Readiness probes and approval acknowledgements use the same request method. A correlated message is **not** proof of human approval: the host must validate Chrome senders and own the final approval channel. + +## Page WebMCP + +CDB exposes native main-document WebMCP through `browser.list_webmcp_tools` and +`browser.invoke_webmcp_tools`. Discovery requires inspect access; invocation requires +interact access and an exclusive lease. Extension hosts can configure discovery with +`defineProvider({ webMcp: { discovery: { include, exclude, enabled } }, ... })`. +Filters hide tools from discovery; they do not block direct invocation by name. +See [native page WebMCP](../../docs/webmcp.md) for references, artifacts, cancellation, +and configuration examples. diff --git a/packages/extension/package.json b/packages/extension/package.json index 3bcd6d9..994d58b 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -69,6 +69,7 @@ "access": "public" }, "devDependencies": { + "@dvcol/cdb-mcp": "workspace:^", "tsdown": "catalog:build", "vitest": "catalog:testing" } diff --git a/packages/extension/src/agent-recovery.ts b/packages/extension/src/agent-recovery.ts index c641f00..d8978de 100644 --- a/packages/extension/src/agent-recovery.ts +++ b/packages/extension/src/agent-recovery.ts @@ -31,6 +31,7 @@ export interface AgentRecovery { /** Reconnects an MV3 agent without treating connections, targets, leases, or sessions as durable state. */ export function createAgentRecovery(options: CreateAgentRecoveryOptions): AgentRecovery { + defineRecovery(options); const minimumBackoffMilliseconds = options.minimumBackoffMilliseconds ?? 250; const maximumBackoffMilliseconds = options.maximumBackoffMilliseconds ?? 30_000; const schedule = options.schedule ?? globalThis.setTimeout; @@ -144,3 +145,8 @@ export function createAgentRecovery = CreateAgentRecoveryOptions>(definition: Definition & CreateAgentRecoveryOptions): Definition { + return definition; +} diff --git a/packages/extension/src/approval-channel.ts b/packages/extension/src/approval-channel.ts index a919bea..ff2a5ee 100644 --- a/packages/extension/src/approval-channel.ts +++ b/packages/extension/src/approval-channel.ts @@ -34,6 +34,7 @@ export interface ExtensionApprovalSenderValidatorOptions { /** Trusts explicitly listed extension-owned views outside tabs, using runtime MessageSender metadata supplied by the host. */ export function createExtensionApprovalSenderValidator(options: ExtensionApprovalSenderValidatorOptions): (sender: ExtensionApprovalSender) => boolean { + defineApprovalSender(options); const documents = new Map(options.allowedDocumentUrls.map((value) => { const url = new URL(value); if (!['chrome-extension:', 'moz-extension:'].includes(url.protocol) || url.username || url.password) @@ -50,6 +51,7 @@ export function createExtensionApprovalSenderValidator(options: ExtensionApprova /** Validates request-only page intents and separately authenticates authority-bearing host UI decisions. */ export function createApprovalChannel(options: ApprovalChannelOptions): ApprovalChannel { + defineApproval(options); return { async receive(message, sender) { if (message === null || typeof message !== 'object' || Array.isArray(message)) return { code: 'APPROVAL_MESSAGE_INVALID', ok: false }; @@ -75,3 +77,19 @@ export function createApprovalChannel(options: ApprovalChannelOptions = ApprovalChannelOptions>(definition: Definition & ApprovalChannelOptions): Definition { + return definition; +} + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineApprovalSender(definition: Definition): Definition { + const documents = definition.allowedDocumentUrls.map((value) => { + const url = new URL(value); + if (!['chrome-extension:', 'moz-extension:'].includes(url.protocol) || url.username || url.password) throw new TypeError('Approval documents must be extension URLs.'); + return url.href; + }); + if (definition.extensionId.length === 0 || documents.length === 0) throw new TypeError('Approval requires an extension identity and at least one document URL.'); + return definition; +} diff --git a/packages/extension/src/bootstrap.ts b/packages/extension/src/bootstrap.ts index 9837a93..fbcad02 100644 --- a/packages/extension/src/bootstrap.ts +++ b/packages/extension/src/bootstrap.ts @@ -115,6 +115,7 @@ export function parseBirpcConnectionOffer(value: unknown): BirpcConnectionOffer /** Relays one origin-bound offer from the page to extension runtime messaging, then removes its listener. */ export function createBirpcOfferContentRelay(options: CreateBirpcOfferContentRelayOptions): BirpcOfferContentRelay { + defineOfferRelay(options); let disposed = false; let receive: (event: MessageEvent) => void; const dispose = (): void => { @@ -135,6 +136,7 @@ export function createBirpcOfferContentRelay(options: CreateBirpcOfferContentRel /** Validates a one-shot runtime offer before allowing the extension agent to open its own direct transport. */ export function createBirpcAgentBootstrap(options: CreateBirpcAgentBootstrapOptions): BirpcAgentBootstrap { + defineBootstrap(options); const now = options.now ?? Date.now; const cancelledNonces = new Set(); const consumedNonces = new Set(); @@ -197,3 +199,13 @@ export function installBirpcOfferRuntimeHandler( }, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineBootstrap = CreateBirpcAgentBootstrapOptions>(definition: Definition & CreateBirpcAgentBootstrapOptions): Definition { + return definition; +} + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineOfferRelay(definition: Definition): Definition { + return definition; +} diff --git a/packages/extension/src/chrome.ts b/packages/extension/src/chrome.ts index 4b933af..28c9e75 100644 --- a/packages/extension/src/chrome.ts +++ b/packages/extension/src/chrome.ts @@ -7,12 +7,16 @@ import type { ProviderConnection } from './provider-connection.js'; import type { CommandAuthorizationPolicy, SelectedTab, SelectedTabPublisher } from './selected-tab-publisher.js'; import type { ChromeTabScopeLifecyclePort } from './tab-scope-lifecycle.js'; import type { TabScopeSelector } from './tab-scope.js'; +import type { WebMcpOptions } from './webmcp.js'; + +import { WebMcpError } from '@dvcol/cdb'; import { sendAgentHeartbeat } from './agent-heartbeat.js'; import { createAgentRecovery } from './agent-recovery.js'; import { createSelectedTabPublisher } from './selected-tab-publisher.js'; import { createTabScopeManager } from './tab-scope-manager.js'; import { parseTabScopeSelector } from './tab-scope.js'; +import { validateWebMcpOptions } from './webmcp.js'; interface ChromeEvent { addListener: (listener: (...arguments_: Arguments) => void) => void; @@ -68,6 +72,7 @@ export function createChromeTabBindings(platform: typeof chrome = chrome): Chrom } export interface ChromeProviderOptions { + readonly webMcp?: WebMcpOptions; readonly chrome?: typeof chrome; readonly connect: () => Promise; readonly maximumLevel: AccessLevel; @@ -109,6 +114,7 @@ export interface ChromeProvider { /** Composes shared publications, claims, navigation renewal, Chrome events and recoverable provider transport. */ export function createChromeProvider(options: ChromeProviderOptions): ChromeProvider { + defineProvider(options); const platform = options.chrome ?? chrome; const tabs = new Map(); const scopes = new Map(); @@ -164,6 +170,7 @@ export function createChromeProvider(options: ChromeProviderOpt let controlled: ControlledTab; const publisher = createSelectedTabPublisher({ capabilities: { level: options.maximumLevel }, + ...(options.webMcp === undefined ? {} : { webMcp: options.webMcp }), scopeId, ...(restoredTarget === undefined ? {} : { targetIdentity: { id: restoredTarget.id, generation: restoredTarget.generation + 1 } }), ...(options.isExposureAllowed === undefined ? {} : { isExposureAllowed: options.isExposureAllowed }), @@ -295,7 +302,7 @@ export function createChromeProvider(options: ChromeProviderOpt const value = await controlled.publisher.executeCommand(command, controller.signal, lease); await source.send({ kind: 'response', method: 'cdp.execute', requestId: message.requestId, protocolVersion: 1, result: { operationId: command.operationId, value } }); } catch (error) { - await source.send({ kind: 'error', method: 'cdp.execute', requestId: message.requestId, protocolVersion: 1, error: { code: 'CDP_COMMAND_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false } }); + await source.send({ kind: 'error', method: 'cdp.execute', requestId: message.requestId, protocolVersion: 1, error: error instanceof WebMcpError ? { code: error.code, message: error.message.slice(0, 1024), retryable: error.retryable } : { code: 'CDP_COMMAND_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false } }); } finally { commands.delete(command.operationId); } @@ -494,3 +501,11 @@ export async function getChromeProviderIdentity(storageKey: string, platform: ty return identity; }); } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineProvider = ChromeProviderOptions>(definition: Definition & ChromeProviderOptions): Definition { + validateWebMcpOptions(definition.webMcp); + return definition; +} + +export type { WebMcpDiscoveryContext, WebMcpOptions, WebMcpPageContext, WebMcpToolMatcher } from './webmcp.js'; diff --git a/packages/extension/src/index.ts b/packages/extension/src/index.ts index 3e6eaef..705131b 100644 --- a/packages/extension/src/index.ts +++ b/packages/extension/src/index.ts @@ -1,5 +1,8 @@ export { type HeartbeatAgentConnection, sendAgentHeartbeat } from './agent-heartbeat.js'; +export { defineRecovery } from './agent-recovery.js'; export { type AgentRecovery, type AgentRecoveryState, createAgentRecovery, type CreateAgentRecoveryOptions, type RecoverableAgentConnection } from './agent-recovery.js'; +export { defineApproval } from './approval-channel.js'; +export { defineApprovalSender } from './approval-channel.js'; export { type ApprovalChannel, type ApprovalChannelOptions, @@ -9,6 +12,8 @@ export { type ExtensionApprovalSender, type ExtensionApprovalSenderValidatorOptions, } from './approval-channel.js'; +export { defineBootstrap } from './bootstrap.js'; +export { defineOfferRelay } from './bootstrap.js'; export { type BirpcAgentBootstrap, type BirpcConnectionOffer, @@ -27,6 +32,7 @@ export { } from './bootstrap.js'; export { type BrokerTabAssignment, type BrokerTabPublisher, createBrokerTabAssignment } from './broker-tab-assignment.js'; export { type ChildSessionRouter, createChildSessionRouter, type PublicChildSession } from './child-session-router.js'; +export { definePairingStore } from './pairing-store.js'; export { createIndexedDbPairingStore, type CreateIndexedDbPairingStoreOptions, @@ -34,12 +40,14 @@ export { type StoredBrokerPairing, } from './pairing-store.js'; export type { ProviderConnection } from './provider-connection.js'; +export { defineSelectedTab } from './selected-tab-lifecycle.js'; export { type ChromeSelectedTabLifecyclePort, createSelectedTabLifecycle, type SelectedTabLifecycle, type SelectedTabLifecycleOptions, } from './selected-tab-lifecycle.js'; +export { definePublisher } from './selected-tab-publisher.js'; export { type ChromeDebuggerPort, type CommandAuthorizationContext, @@ -49,13 +57,18 @@ export { type SelectedTabPublisher, type SelectedTabPublisherOptions, } from './selected-tab-publisher.js'; +export { defineTabLifecycle } from './tab-scope-lifecycle.js'; export { type ChromeTabScopeLifecyclePort, createTabScopeLifecycle, type TabScopeLifecycle, type TabScopeLifecycleOptions, } from './tab-scope-lifecycle.js'; +export { defineTabScope } from './tab-scope-manager.js'; export { createTabScopeManager, type TabScopeManager, type TabScopeManagerOptions } from './tab-scope-manager.js'; export { matchesTabScope, parseTabScopeSelector, type TabScopeSelector } from './tab-scope.js'; +export { definePageRequest } from './page-request.js'; export { createPageRequestBridge, type PageRequestBridge, type PageRequestBridgeOptions, type PageRequestOptions } from './page-request.js'; + +export type { WebMcpDiscoveryContext, WebMcpOptions, WebMcpPageContext, WebMcpToolMatcher } from './webmcp.js'; diff --git a/packages/extension/src/notification-theme.ts b/packages/extension/src/notification-theme.ts index 7c281a3..a2a68f8 100644 --- a/packages/extension/src/notification-theme.ts +++ b/packages/extension/src/notification-theme.ts @@ -98,6 +98,9 @@ export const browserControlNotificationStyles = ` box-shadow: var(--cdb-shadow); text-align: right; } + .request { + cursor: pointer; + } h2 { margin: 0 calc(-1 * var(--cdb-spacing)) 10px; padding: 10px 42px 10px var(--cdb-spacing); diff --git a/packages/extension/src/notifications.ts b/packages/extension/src/notifications.ts index 1ff207e..febff2e 100644 --- a/packages/extension/src/notifications.ts +++ b/packages/extension/src/notifications.ts @@ -15,6 +15,7 @@ export interface BrowserControlNotification { export interface BrowserControlNotificationController { dismiss: (requestId: string) => void; dispose: () => void; + reject: (requestId: string) => Promise; review: (requestId: string) => Promise; revoke: (requestId: string) => Promise; snapshot: () => BrowserControlNotification; @@ -26,11 +27,14 @@ export interface BrowserControlNotificationOptions { readonly targetId?: string; /** Opens the embedding application's approval UI. This callback does not itself grant authority. */ readonly onReview: (request: BrokerRequest) => void | Promise; + /** Authoritatively denies a pending request. Distinct from `dismiss`, which never rejects. */ + readonly onReject: (requestId: string) => Promise; readonly onRevoke: (requestId: string) => Promise; } /** Derives notification state from the broker. Dismissal is presentation-only and never rejects a request. */ export function createBrowserControlNotificationController(options: BrowserControlNotificationOptions): BrowserControlNotificationController { + defineNotifications(options); const dismissed = new Set(); const listeners = new Set<(value: BrowserControlNotification) => void>(); let state: Pick | undefined; @@ -72,6 +76,11 @@ export function createBrowserControlNotificationController(options: BrowserContr if (request === undefined) throw new Error('The browser access request is no longer pending.'); await options.onReview(request); }, + async reject(requestId) { + const request = snapshot().requests.find(candidate => candidate.id === requestId); + if (request === undefined) throw new Error('The browser access request is no longer pending.'); + await options.onReject(requestId); + }, async revoke(requestId) { if (!snapshot().grants.some(grant => grant.requestId === requestId)) throw new Error('The approved scope is no longer present.'); await options.onRevoke(requestId); @@ -98,7 +107,7 @@ export interface BrowserControlNotificationRendererOptions { /** Describe the host's action, including direct approval when the host provides that policy. */ readonly reviewLabel?: (request: BrokerRequest) => string; readonly clientLabel?: (request: BrokerRequest) => string; - /** Rejects the pending request through the host; dismissal remains local. */ + /** Overrides the authoritative reject action; defaults to `controller.reject`. Dismissal remains local regardless. */ readonly onReject?: (request: BrokerRequest) => Promise; /** Host-supplied CSS, applied inside the isolated notification root. */ readonly css?: string; @@ -110,8 +119,9 @@ export interface BrowserControlNotificationRenderer { setColorMode: (mode: BrowserControlNotificationColorMode) => void; } -/** Optional themed presentation. Review buttons delegate to the host's trusted final approval UI. */ +/** Optional themed presentation. Pending request cards delegate to the host's trusted final approval UI. */ export function renderBrowserControlNotifications(options: BrowserControlNotificationRendererOptions): BrowserControlNotificationRenderer { + defineNotificationRenderer(options); const document = options.container.ownerDocument; const host = document.createElement('section'); host.dataset.cdbNotifications = ''; @@ -139,12 +149,10 @@ export function renderBrowserControlNotifications(options: BrowserControlNotific let disposed = false; let renderedNotification: string | undefined; - async function runAction( - button: HTMLButtonElement, + async function invokeAction( container: HTMLElement, action: () => void | Promise, ): Promise { - button.disabled = true; try { await action(); } catch (error) { @@ -153,6 +161,17 @@ export function renderBrowserControlNotifications(options: BrowserControlNotific message.setAttribute('role', 'alert'); message.textContent = error instanceof Error ? error.message : 'The browser request could not be completed.'; container.append(message); + } + } + + async function runAction( + button: HTMLButtonElement, + container: HTMLElement, + action: () => void | Promise, + ): Promise { + button.disabled = true; + try { + await invokeAction(container, action); } finally { button.disabled = false; } @@ -166,7 +185,8 @@ export function renderBrowserControlNotifications(options: BrowserControlNotific const button = document.createElement('button'); button.type = 'button'; button.textContent = label; - button.onclick = () => { + button.onclick = (event) => { + event.stopPropagation(); void runAction(button, container, action); }; container.append(button); @@ -216,6 +236,10 @@ export function renderBrowserControlNotifications(options: BrowserControlNotific content.replaceChildren(); for (const request of notification.requests) { const element = createCard(options.branding?.title ?? 'Browser control requested'); + element.className = 'request'; + element.onclick = () => { + void invokeAction(element, async () => options.controller.review(request.id)); + }; const dismiss = createActionButton(element, '×', () => options.controller.dismiss(request.id)); dismiss.className = 'dismiss'; dismiss.setAttribute('aria-label', 'Dismiss'); @@ -224,8 +248,7 @@ export function renderBrowserControlNotifications(options: BrowserControlNotific const actions = document.createElement('footer'); element.append(actions); createActionButton(actions, options.reviewLabel?.(request) ?? 'Review request', async () => options.controller.review(request.id)); - const reject = options.onReject; - if (reject !== undefined) createActionButton(actions, 'Reject', async () => reject(request)); + createActionButton(actions, 'Reject', async () => (options.onReject ?? (async r => options.controller.reject(r.id)))(request)); content.append(element); } const grouped = Map.groupBy(notification.grants, grant => grant.requestId); @@ -250,3 +273,13 @@ export function renderBrowserControlNotifications(options: BrowserControlNotific }, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineNotifications(definition: Definition): Definition { + return definition; +} + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineNotificationRenderer(definition: Definition): Definition { + return definition; +} diff --git a/packages/extension/src/page-request.ts b/packages/extension/src/page-request.ts index 5bf2f97..4370b9b 100644 --- a/packages/extension/src/page-request.ts +++ b/packages/extension/src/page-request.ts @@ -19,6 +19,7 @@ export interface PageRequestBridge { /** Correlation and acknowledgement are transport mechanics, never proof of trusted human approval. */ export function createPageRequestBridge(options: PageRequestBridgeOptions): PageRequestBridge { + definePageRequest(options); const correlationKey = options.correlationKey ?? 'requestId'; const lifetime = new AbortController(); return { @@ -56,3 +57,8 @@ export function createPageRequestBridge(options: PageRequestBridgeOptions): Page function record(value: unknown): Readonly> | undefined { return value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record : undefined; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function definePageRequest(definition: Definition): Definition { + return definition; +} diff --git a/packages/extension/src/pairing-store.ts b/packages/extension/src/pairing-store.ts index a3b0a77..2c5a9a8 100644 --- a/packages/extension/src/pairing-store.ts +++ b/packages/extension/src/pairing-store.ts @@ -77,6 +77,7 @@ function isValidPairing(value: unknown): value is StoredBrokerPairing { export function createIndexedDbPairingStore( options: CreateIndexedDbPairingStoreOptions = {}, ): IndexedDbPairingStore { + definePairingStore(options); const databaseName = options.databaseName ?? 'chrome-debugger-bridge'; return { @@ -133,3 +134,8 @@ export function createIndexedDbPairingStore( }, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function definePairingStore(definition: Definition): Definition { + return definition; +} diff --git a/packages/extension/src/selected-tab-lifecycle.ts b/packages/extension/src/selected-tab-lifecycle.ts index c559563..b80d6cd 100644 --- a/packages/extension/src/selected-tab-lifecycle.ts +++ b/packages/extension/src/selected-tab-lifecycle.ts @@ -31,6 +31,7 @@ export interface SelectedTabLifecycleOptions { /** Binds Chrome lifecycle events to the selected target without exposing tab identifiers to broker code. */ export function createSelectedTabLifecycle(options: SelectedTabLifecycleOptions): SelectedTabLifecycle { + defineSelectedTab(options); let started = false; function reportFailure(task: Promise): void { @@ -72,3 +73,8 @@ export function createSelectedTabLifecycle(options: SelectedTabLifecycleOptions) }, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineSelectedTab(definition: Definition): Definition { + return definition; +} diff --git a/packages/extension/src/selected-tab-publisher.ts b/packages/extension/src/selected-tab-publisher.ts index 1418f23..e7fd201 100644 --- a/packages/extension/src/selected-tab-publisher.ts +++ b/packages/extension/src/selected-tab-publisher.ts @@ -3,12 +3,15 @@ import type { CapabilityGrant, CdpCommand, JsonObject, Lease, PublishedTarget } import type { PublicChildSession } from './child-session-router.js'; import type { AgentControlPresentationEvent } from './presentation.js'; import type { TabScopeSelector } from './tab-scope.js'; +import type { WebMcpController, WebMcpOptions } from './webmcp.js'; +import { WebMcpError, webMcpMethods } from '@dvcol/cdb'; import { isCdpNameAllowed, requiredLeaseMode } from '@dvcol/cdb/cdp-catalogue'; import { createChildSessionRouter } from './child-session-router.js'; import { translateAgentControlInputCommand } from './presentation.js'; import { matchesTabScope } from './tab-scope.js'; +import { createWebMcpController, validateWebMcpOptions } from './webmcp.js'; export interface ChromeDebuggerPort { attach: (target: { readonly sessionId?: string; readonly tabId: number }, requiredVersion: string) => Promise | void; @@ -42,6 +45,7 @@ export interface CommandAuthorizationContext { export type CommandAuthorizationPolicy = (context: CommandAuthorizationContext) => boolean | Promise; export interface SelectedTabPublisherOptions { + readonly webMcp?: WebMcpOptions; readonly capabilities: CapabilityGrant; readonly chromeDebugger: ChromeDebuggerPort; /** Can make a published target's command authorization stricter, but never relax the bridge kernel. */ @@ -107,6 +111,7 @@ function isSupportedPage(url: string | undefined): boolean { /** Keeps Chrome's tab identifier in this closure and publishes only a lifecycle-bound opaque target. */ export function createSelectedTabPublisher(options: SelectedTabPublisherOptions): SelectedTabPublisher { + definePublisher(options); const maximumResultBytes = options.maximumResultBytes ?? 16_777_216; if (!Number.isSafeInteger(maximumResultBytes) || maximumResultBytes < 1) throw new TypeError('maximumResultBytes must be a positive safe integer.'); const childSessionRouter = createChildSessionRouter(); @@ -115,9 +120,19 @@ export function createSelectedTabPublisher(options: SelectedTabPublisherOptions) const activeChildSubscriptionDemands = new Map>(); let selectedTabId: number | undefined; let publishedTarget: PublishedTarget | undefined; + let webMcp: WebMcpController | undefined; let publicationReady = false; const pendingEvents: { readonly method: string; readonly parameters: JsonObject; readonly sessionId?: string }[] = []; + function webMcpController(target: PublishedTarget, tabId: number): WebMcpController { + return createWebMcpController({ + target, + maximumResultBytes, + ...(options.webMcp === undefined ? {} : { policy: options.webMcp }), + sendCommand: async (method, parameters) => options.chromeDebugger.sendCommand({ tabId }, method, parameters), + }); + } + function getRedactedMetadata(tab: Omit): Pick { return options.metadataPolicy?.(tab) ?? {}; } @@ -214,6 +229,9 @@ export function createSelectedTabPublisher(options: SelectedTabPublisherOptions) } const targetToRevoke = publishedTarget; const tabIdToDetach = selectedTabId; + const previousWebMcp = webMcp; + webMcp = undefined; + const webMcpDisposed = previousWebMcp?.dispose(); publishedTarget = undefined; selectedTabId = undefined; publicationReady = false; @@ -224,6 +242,7 @@ export function createSelectedTabPublisher(options: SelectedTabPublisherOptions) activeChildSubscriptionDemands.clear(); try { await options.revokeTarget(targetToRevoke, reason); + await webMcpDisposed; } finally { try { await options.chromeDebugger.detach({ tabId: tabIdToDetach }); @@ -270,6 +289,15 @@ export function createSelectedTabPublisher(options: SelectedTabPublisherOptions) if (!isBaselineCommandAuthorized(authorizationContext) || !isPolicyAuthorized) { throw new Error('The requested command is not permitted.'); } + if (command.method === webMcpMethods.list || command.method === webMcpMethods.invoke) { + if (command.sessionId !== undefined || webMcp === undefined) throw new WebMcpError('FEATURE_UNSUPPORTED', 'WebMCP bridge operations target the main document only.'); + const result = await webMcp.execute(command.method, command.parameters, abortSignal); + if (publishedTarget?.id !== command.targetId || publishedTarget.generation !== command.targetGeneration) + throw new WebMcpError(command.method === webMcpMethods.invoke ? 'WEBMCP_OUTCOME_UNKNOWN' : 'WEBMCP_TOOL_STALE', 'The target generation changed during the WebMCP operation.'); + if (new TextEncoder().encode(JSON.stringify(result)).byteLength > maximumResultBytes) + throw new WebMcpError(command.method === webMcpMethods.invoke ? 'WEBMCP_OUTCOME_UNKNOWN' : 'WEBMCP_DISCOVERY_FAILED', 'The WebMCP result exceeded the configured size bound. Do not replay an invocation to recover its result.'); + return result; + } if (command.method === 'Bridge.listChildSessions') { return { sessions: childSessionRouter.list().map(session => ({ ...session })) }; } @@ -310,6 +338,10 @@ export function createSelectedTabPublisher(options: SelectedTabPublisherOptions) async function setSubscriptionDemand(methodPrefix: string, active: boolean, sessionId?: string): Promise { if (selectedTabId === undefined) throw new Error('The requested target is not available.'); + if (methodPrefix === 'WebMCP.') { + if (sessionId !== undefined || webMcp === undefined) throw new WebMcpError('FEATURE_UNSUPPORTED', 'WebMCP bridge operations target the main document only.'); + return webMcp.setActive(active); + } const domain = methodPrefix.split('.', 1)[0]; if (domain === undefined || !domainNamePattern.test(domain)) throw new Error('The subscription method is invalid.'); const chromeSessionId = sessionId === undefined ? undefined : childSessionRouter.resolve(sessionId); @@ -360,6 +392,9 @@ export function createSelectedTabPublisher(options: SelectedTabPublisherOptions) activeRootSubscriptionDemands.clear(); activeChildSubscriptionDemands.clear(); publishedTarget = renewedTarget; + const previousWebMcp = webMcp; + webMcp = webMcpController(renewedTarget, selectedTabId); + await previousWebMcp?.dispose(); publicationReady = false; pendingEvents.length = 0; try { @@ -367,6 +402,8 @@ export function createSelectedTabPublisher(options: SelectedTabPublisherOptions) await options.publishTarget(renewedTarget); finishPublication(); } catch (error) { + await webMcp?.dispose(); + webMcp = undefined; publishedTarget = priorTarget; pendingEvents.length = 0; throw error; @@ -382,6 +419,7 @@ export function createSelectedTabPublisher(options: SelectedTabPublisherOptions) }, debuggerEvent(source, method, parameters) { if (source.tabId !== undefined && source.tabId !== selectedTabId) return; + if (source.sessionId === undefined) webMcp?.event(method, parameters); if (method === 'Target.attachedToTarget') { const childSession = handleAttachedChild(parameters, source.sessionId); if (childSession !== undefined && isEligibleChildAttachment(parameters)) { @@ -437,12 +475,15 @@ export function createSelectedTabPublisher(options: SelectedTabPublisherOptions) selectedTabId = tab.tabId; publishedTarget = target; + webMcp = webMcpController(target, tab.tabId); try { await configureFlatSessions(); await options.publishTarget(target); finishPublication(); } catch (error) { childSessionRouter.revoke(); + await webMcp?.dispose(); + webMcp = undefined; publishedTarget = undefined; selectedTabId = undefined; publicationReady = false; @@ -495,6 +536,9 @@ export function createSelectedTabPublisher(options: SelectedTabPublisherOptions) await options.revokeTarget(publishedTarget, 'explicit'); await options.chromeDebugger.attach({ tabId }, '1.3'); publishedTarget = renewedTarget; + const previousWebMcp = webMcp; + webMcp = webMcpController(renewedTarget, tabId); + await previousWebMcp?.dispose(); await configureFlatSessions(); await options.publishTarget(renewedTarget); finishPublication(); @@ -514,3 +558,11 @@ export function createSelectedTabPublisher(options: SelectedTabPublisherOptions) setSubscriptionDemand, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function definePublisher(definition: Definition): Definition { + const maximumResultBytes = definition.maximumResultBytes ?? 16_777_216; + if (!Number.isSafeInteger(maximumResultBytes) || maximumResultBytes < 1) throw new TypeError('maximumResultBytes must be a positive safe integer.'); + validateWebMcpOptions(definition.webMcp); + return definition; +} diff --git a/packages/extension/src/tab-scope-lifecycle.ts b/packages/extension/src/tab-scope-lifecycle.ts index 2a20a7c..bdc1289 100644 --- a/packages/extension/src/tab-scope-lifecycle.ts +++ b/packages/extension/src/tab-scope-lifecycle.ts @@ -38,6 +38,7 @@ export interface TabScopeLifecycleOptions { /** Publishes one selector through the same race-safe lifecycle used by shared scope managers. */ export function createTabScopeLifecycle(options: TabScopeLifecycleOptions): TabScopeLifecycle { + defineTabLifecycle(options); let manager: TabScopeManager | undefined; let cleanup = Promise.resolve(); let started = false; @@ -71,3 +72,8 @@ export function createTabScopeLifecycle(options: TabScopeLifecycleOptions): TabS }, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineTabLifecycle(definition: Definition): Definition { + return definition; +} diff --git a/packages/extension/src/tab-scope-manager.ts b/packages/extension/src/tab-scope-manager.ts index 6c3a413..40fcf88 100644 --- a/packages/extension/src/tab-scope-manager.ts +++ b/packages/extension/src/tab-scope-manager.ts @@ -41,6 +41,7 @@ interface ManagedTab { /** Shares physical debugger attachments while keeping independently approved scope membership separate. */ export function createTabScopeManager(options: TabScopeManagerOptions): TabScopeManager { + defineTabScope(options); const scopes = new Map(); const tabs = new Map(); const operations = new Map>(); @@ -261,3 +262,8 @@ export function createTabScopeManager(options: TabScopeManagerOptions): TabScope }, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineTabScope(definition: Definition): Definition { + return definition; +} diff --git a/packages/extension/src/webmcp.ts b/packages/extension/src/webmcp.ts new file mode 100644 index 0000000..84d4410 --- /dev/null +++ b/packages/extension/src/webmcp.ts @@ -0,0 +1,399 @@ +import type { JsonObject, JsonValue, PublishedTarget, WebMcpTool } from '@dvcol/cdb'; + +import { WebMcpError, webMcpMethods } from '@dvcol/cdb'; + +export interface WebMcpPageContext { + readonly targetId: string; + readonly targetGeneration: number; + readonly url: string; +} + +export interface WebMcpDiscoveryContext extends WebMcpPageContext { + readonly tool: WebMcpTool; +} + +export type WebMcpToolMatcher = string | RegExp | ((context: WebMcpDiscoveryContext) => boolean | Promise); + +export interface WebMcpOptions { + /** Controls discovery only; an authorized caller may still invoke a known tool name. */ + readonly discovery?: { + readonly enabled?: boolean | ((context: WebMcpPageContext) => boolean | Promise); + readonly include?: readonly WebMcpToolMatcher[]; + readonly exclude?: readonly WebMcpToolMatcher[]; + }; +} + +/** Checks policy shapes without evaluating host callbacks. */ +export function validateWebMcpOptions(options: WebMcpOptions | undefined): void { + const policy = options?.discovery; + if (policy === undefined) return; + if (policy.enabled !== undefined && typeof policy.enabled !== 'boolean' && typeof policy.enabled !== 'function') + throw new TypeError('WebMCP discovery.enabled must be a boolean or callback.'); + for (const rules of [policy.include, policy.exclude]) { + if (rules === undefined) continue; + if (!Array.isArray(rules) || rules.some(rule => typeof rule !== 'string' && typeof rule !== 'function' && !(rule instanceof RegExp))) + throw new TypeError('WebMCP discovery rules must be arrays of names, regular expressions, or callbacks.'); + } +} + +interface MainDocument { + readonly frameId: string; + readonly loaderId: string; + readonly reference: string; + readonly url: string; +} + +interface PendingInvocation { + readonly reject: (error: Error) => void; + readonly resolve: (value: JsonObject) => void; +} + +export interface WebMcpController { + setActive: (active: boolean) => Promise; + execute: (method: string, parameters: JsonObject | undefined, signal: AbortSignal) => Promise; + event: (method: string, parameters: JsonObject) => void; + dispose: () => Promise; +} + +function objectValue(value: JsonValue | undefined): JsonObject | undefined { + if (value === null || typeof value !== 'object' || Array.isArray(value)) return undefined; + return value; +} + +async function matches(rule: WebMcpToolMatcher, context: WebMcpDiscoveryContext): Promise { + if (typeof rule === 'string') return rule === context.tool.name; + if (rule instanceof RegExp) return new RegExp(rule.source, rule.flags).test(context.tool.name); + return rule(context); +} + +async function matchesAny(rules: readonly WebMcpToolMatcher[], context: WebMcpDiscoveryContext): Promise { + for (const rule of rules) if (await matches(rule, context)) return true; + return false; +} + +async function untilAborted(operation: Promise, signal: AbortSignal, cancellationError = new WebMcpError('REQUEST_CANCELLED', 'The WebMCP operation was cancelled.')): Promise { + const cancelled = Promise.withResolvers(); + const abort = (): void => cancelled.reject(cancellationError); + signal.addEventListener('abort', abort, { once: true }); + if (signal.aborted) abort(); + try { + return await Promise.race([operation, cancelled.promise]); + } finally { + signal.removeEventListener('abort', abort); + } +} + +/** Owns native main-document state beneath one publisher's target-generation boundary. */ +export function createWebMcpController(options: { + readonly target: Pick; + readonly policy?: WebMcpOptions; + readonly maximumResultBytes: number; + readonly sendCommand: (method: string, parameters?: JsonObject) => Promise; +}): WebMcpController { + const tools = new Map(); + const references = new Map(); + const referencesByName = new Map(); + const pending = new Map(); + const lifetime = new AbortController(); + let document: MainDocument | undefined; + let enabled = false; + let closed = false; + let catalogueError: WebMcpError | undefined; + let queue = Promise.resolve(); + let dispatching = 0; + let catalogueRevision = 0; + let earlyResponseOverflow = false; + const earlyResponses = new Map(); + + function checkOpen(): void { + if (closed) throw new WebMcpError('WEBMCP_TOOL_STALE', 'The WebMCP target generation is no longer available.', true); + } + + function cancelNative(invocationId: string): void { + void options.sendCommand('WebMCP.cancelInvocation', { invocationId }).catch(() => {}); + } + + function invalidate(): void { + document = undefined; + catalogueRevision += 1; + earlyResponses.clear(); + earlyResponseOverflow = false; + tools.clear(); + references.clear(); + referencesByName.clear(); + catalogueError = undefined; + for (const [invocationId, invocation] of pending) { + invocation.reject(new WebMcpError('WEBMCP_OUTCOME_UNKNOWN', 'The document changed before the tool result was received. Do not replay the invocation.')); + cancelNative(invocationId); + } + pending.clear(); + } + + async function serialize(operation: () => Promise): Promise { + const result = queue.catch(() => {}).then(operation); + queue = result; + return result; + } + + async function readDocument(): Promise<{ frameId: string; loaderId: string; url: string }> { + const result = await options.sendCommand('Page.getFrameTree'); + checkOpen(); + const frame = objectValue(objectValue(result.frameTree)?.frame); + if (typeof frame?.id !== 'string' || typeof frame.loaderId !== 'string' || typeof frame.url !== 'string') + throw new WebMcpError('WEBMCP_DISCOVERY_FAILED', 'The main document could not be identified.'); + return { frameId: frame.id, loaderId: frame.loaderId, url: frame.url }; + } + + async function prepare(): Promise { + checkOpen(); + const current = await readDocument(); + if (document?.frameId !== current.frameId || document.loaderId !== current.loaderId) { + invalidate(); + if (enabled) { + await options.sendCommand('WebMCP.disable'); + enabled = false; + checkOpen(); + } + document = { ...current, reference: crypto.randomUUID() }; + } else { + document = { ...document, url: current.url }; + } + if (enabled) return; + tools.clear(); + catalogueError = undefined; + const selectedDocument = document.reference; + try { + await options.sendCommand('WebMCP.enable'); + } catch (error) { + throw new WebMcpError('FEATURE_UNSUPPORTED', `Native WebMCP is unavailable: ${error instanceof Error ? error.message : String(error)}`); + } + enabled = true; + checkOpen(); + if (document?.reference !== selectedDocument) + throw new WebMcpError('WEBMCP_TOOL_STALE', 'The main document changed during WebMCP discovery.', true); + for (const [reference, entry] of references) { + if (tools.has(entry.name)) continue; + references.delete(reference); + referencesByName.delete(entry.name); + } + } + + function checkDocument(reference: string): MainDocument { + checkOpen(); + if (document?.reference !== reference) throw new WebMcpError('WEBMCP_TOOL_STALE', 'The main document changed. List its WebMCP tools again.', true); + return document; + } + + async function verifyDocument(reference: string): Promise { + const selected = checkDocument(reference); + const current = await readDocument(); + if (selected.frameId !== current.frameId || selected.loaderId !== current.loaderId) invalidate(); + checkDocument(reference); + } + + function pageContext(selected: MainDocument): WebMcpPageContext { + return { targetId: options.target.id, targetGeneration: options.target.generation, url: selected.url }; + } + + function describe(name: string, value: JsonObject, selected: MainDocument): WebMcpTool { + let toolRef = referencesByName.get(name); + if (toolRef === undefined) { + toolRef = crypto.randomUUID(); + referencesByName.set(name, toolRef); + references.set(toolRef, { document: selected.reference, name }); + } + const inputSchema = objectValue(value.inputSchema); + const annotations = objectValue(value.annotations); + return { + name, + description: typeof value.description === 'string' ? value.description : '', + toolRef, + ...(inputSchema === undefined ? {} : { inputSchema: structuredClone(inputSchema) }), + ...(annotations === undefined ? {} : { annotations: structuredClone(annotations) }), + }; + } + + async function list(selected: MainDocument, signal: AbortSignal): Promise { + const revision = catalogueRevision; + if (catalogueError !== undefined) throw catalogueError; + const policy = options.policy?.discovery; + let visible: WebMcpTool[] = []; + let discoveryEnabled: boolean; + try { + discoveryEnabled = typeof policy?.enabled === 'function' ? await policy.enabled(pageContext(selected)) : policy?.enabled ?? true; + signal.throwIfAborted(); + if (discoveryEnabled) { + for (const [name, value] of [...tools].sort(([left], [right]) => left.localeCompare(right))) { + signal.throwIfAborted(); + const tool = describe(name, value, selected); + const context = { ...pageContext(selected), tool }; + if (policy?.include !== undefined && !await matchesAny(policy.include, context)) continue; + if (policy?.exclude !== undefined && await matchesAny(policy.exclude, context)) continue; + signal.throwIfAborted(); + visible.push(tool); + } + } + } catch (error) { + visible = []; + throw new WebMcpError('WEBMCP_DISCOVERY_FAILED', `WebMCP discovery policy failed: ${error instanceof Error ? error.message : String(error)}`); + } + await verifyDocument(selected.reference); + if (revision !== catalogueRevision) throw new WebMcpError('WEBMCP_TOOL_STALE', 'The tool catalogue changed during discovery. List tools again.', true); + return { documentRef: selected.reference, enabled: discoveryEnabled, tools: visible.map(tool => ({ ...tool })) }; + } + + async function invoke(parameters: JsonObject | undefined, selected: MainDocument, signal: AbortSignal): Promise { + const toolName = parameters?.toolName; + const toolRef = parameters?.toolRef; + const input = objectValue(parameters?.input); + if (input === undefined || (toolName !== undefined && typeof toolName !== 'string') || (toolRef !== undefined && typeof toolRef !== 'string') || (typeof toolName === 'string') === (typeof toolRef === 'string') || Object.keys(parameters ?? {}).some(key => !['input', 'toolName', 'toolRef'].includes(key))) + throw new WebMcpError('CDP_COMMAND_FAILED', 'Provide an input object and exactly one toolName or toolRef.'); + const reference = typeof toolRef === 'string' ? references.get(toolRef) : undefined; + if (typeof toolRef === 'string' && reference?.document !== selected.reference) + throw new WebMcpError('WEBMCP_TOOL_STALE', 'The WebMCP tool reference is stale. List tools again.', true); + const name = typeof toolName === 'string' ? toolName : reference?.name; + if (name === undefined || name.length === 0) throw new WebMcpError('CDP_COMMAND_FAILED', 'A nonempty WebMCP tool name is required.'); + await verifyDocument(selected.reference); + signal.throwIfAborted(); + dispatching += 1; + let response: JsonObject; + try { + const nativeResponse = options.sendCommand('WebMCP.invokeTool', { frameId: selected.frameId, toolName: name, input }); + void nativeResponse.then((value) => { + if ((closed || signal.aborted || document?.reference !== selected.reference) && typeof value.invocationId === 'string') cancelNative(value.invocationId); + }).catch(() => {}); + response = await untilAborted(nativeResponse, signal, new WebMcpError('WEBMCP_OUTCOME_UNKNOWN', 'The invocation was interrupted after dispatch. Do not replay it.')); + } catch (error) { + if (error instanceof WebMcpError) throw error; + throw new WebMcpError('WEBMCP_OUTCOME_UNKNOWN', 'Chrome did not return a reliable invocation acknowledgement. Do not replay it.'); + } finally { + dispatching -= 1; + } + const invocationId = response.invocationId; + if (typeof invocationId !== 'string') throw new WebMcpError('WEBMCP_OUTCOME_UNKNOWN', 'Chrome accepted the invocation without a usable invocation identifier. Do not replay it.'); + if (closed || signal.aborted || document?.reference !== selected.reference) { + cancelNative(invocationId); + throw new WebMcpError('WEBMCP_OUTCOME_UNKNOWN', 'The invocation was interrupted after dispatch. Do not replay it.'); + } + if (earlyResponseOverflow) { + cancelNative(invocationId); + if (dispatching === 0) { + earlyResponseOverflow = false; + earlyResponses.clear(); + } + throw new WebMcpError('WEBMCP_OUTCOME_UNKNOWN', 'Native tool completion exceeded the result buffer. Do not replay the invocation.'); + } + const result = Promise.withResolvers(); + pending.set(invocationId, result); + const earlyResponse = earlyResponses.get(invocationId); + earlyResponses.delete(invocationId); + if (dispatching === 0) earlyResponses.clear(); + if (earlyResponse !== undefined) settleInvocation(earlyResponse); + const abort = (): void => { + cancelNative(invocationId); + result.reject(new WebMcpError('WEBMCP_OUTCOME_UNKNOWN', 'Cancellation was requested after tool dispatch; its side effects may already have occurred.')); + }; + signal.addEventListener('abort', abort, { once: true }); + try { + const value = await result.promise; + try { + await verifyDocument(selected.reference); + } catch { + throw new WebMcpError('WEBMCP_OUTCOME_UNKNOWN', 'The document changed before the tool result could be verified. Do not replay it.'); + } + if (signal.aborted) throw new WebMcpError('WEBMCP_OUTCOME_UNKNOWN', 'The invocation was interrupted after dispatch. Do not replay it.'); + return value; + } finally { + pending.delete(invocationId); + signal.removeEventListener('abort', abort); + } + } + + function settleInvocation(parameters: JsonObject): void { + if (typeof parameters.invocationId !== 'string') return; + const invocation = pending.get(parameters.invocationId); + if (invocation === undefined) return; + if (parameters.status === 'Completed') { + invocation.resolve({ status: 'completed', ...(parameters.output === undefined ? {} : { output: parameters.output }) }); + return; + } + if (parameters.status === 'Canceled') { + invocation.reject(new WebMcpError('REQUEST_CANCELLED', 'Chrome cancelled the WebMCP invocation.')); + return; + } + invocation.reject(new WebMcpError('CDP_COMMAND_FAILED', typeof parameters.errorText === 'string' ? parameters.errorText.slice(0, 1024) : 'The WebMCP tool failed.')); + } + + return { + async setActive(active) { + return serialize(async () => { + if (active) return prepare(); + if (!enabled) return; + await options.sendCommand('WebMCP.disable'); + enabled = false; + tools.clear(); + }); + }, + async execute(method, parameters, signal) { + const combinedSignal = AbortSignal.any([signal, lifetime.signal]); + await untilAborted(serialize(prepare), combinedSignal); + const selected = document; + if (selected === undefined) throw new WebMcpError('WEBMCP_TOOL_STALE', 'The main document is no longer available.', true); + if (method === webMcpMethods.list) { + if (Object.keys(parameters ?? {}).length > 0) throw new WebMcpError('CDP_COMMAND_FAILED', 'WebMCP discovery accepts no frame or additional parameters.'); + return untilAborted(list(selected, combinedSignal), combinedSignal); + } + if (method === webMcpMethods.invoke) return invoke(parameters, selected, combinedSignal); + throw new WebMcpError('FEATURE_UNSUPPORTED', 'Unknown WebMCP bridge operation.'); + }, + event(method, parameters) { + if (closed) return; + if (method === 'Page.frameNavigated') { + const frame = objectValue(parameters.frame); + if (frame !== undefined && frame.parentId === undefined && (frame.id !== document?.frameId || frame.loaderId !== document?.loaderId)) invalidate(); + return; + } + if (method === 'WebMCP.toolsAdded' && Array.isArray(parameters.tools)) { + for (const candidate of parameters.tools) { + const tool = objectValue(candidate); + if (tool === undefined || tool.frameId !== document?.frameId || typeof tool.name !== 'string') continue; + catalogueRevision += 1; + tools.set(tool.name, tool); + } + if (new TextEncoder().encode(JSON.stringify([...tools.values()])).byteLength > options.maximumResultBytes) { + tools.clear(); + catalogueError = new WebMcpError('WEBMCP_DISCOVERY_FAILED', 'The native WebMCP catalogue exceeded the configured result size bound.'); + } + } + if (method === 'WebMCP.toolsRemoved' && Array.isArray(parameters.tools)) { + for (const candidate of parameters.tools) { + const tool = objectValue(candidate); + if (tool?.frameId !== document?.frameId || typeof tool?.name !== 'string') continue; + catalogueRevision += 1; + tools.delete(tool.name); + const reference = referencesByName.get(tool.name); + if (reference !== undefined) references.delete(reference); + referencesByName.delete(tool.name); + } + } + if (method !== 'WebMCP.toolResponded' || typeof parameters.invocationId !== 'string') return; + if (!pending.has(parameters.invocationId) && dispatching > 0) { + const size = new TextEncoder().encode(JSON.stringify([...earlyResponses.values(), parameters])).byteLength; + if (size <= options.maximumResultBytes) earlyResponses.set(parameters.invocationId, parameters); + else earlyResponseOverflow = true; + return; + } + settleInvocation(parameters); + }, + async dispose() { + closed = true; + invalidate(); + lifetime.abort(); + return serialize(async () => { + if (!enabled) return; + enabled = false; + await options.sendCommand('WebMCP.disable').catch(() => {}); + }); + }, + }; +} diff --git a/packages/extension/test/config.test.ts b/packages/extension/test/config.test.ts new file mode 100644 index 0000000..0c3f4d6 --- /dev/null +++ b/packages/extension/test/config.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { defineApprovalSender } from '../src/approval-channel.js'; +import { defineProvider } from '../src/chrome.js'; +import { createSelectedTabPublisher, definePublisher } from '../src/selected-tab-publisher.js'; + +describe('configuration definitions', () => { + it('preserves provider configuration and callback types without touching Chrome', () => { + expect.assertions(4); + const connect = vi.fn(); + const authorizeApproval = vi.fn((_request: unknown, _selector: unknown, context: { accepted: boolean }) => context.accepted); + const configuration = { connect, authorizeApproval, maximumLevel: 'interact' as const }; + const definition = defineProvider<{ accepted: boolean }, typeof configuration>(configuration); + expect(definition).toBe(configuration); + expect(definition.maximumLevel).toBe('interact'); + expect(connect).not.toHaveBeenCalled(); + expect(authorizeApproval).not.toHaveBeenCalled(); + }); + + it('rejects invalid bounds equally before the publisher touches its dependencies', () => { + expect.assertions(2); + const options = { maximumResultBytes: 0 } as Parameters[0]; + expect(() => definePublisher(options)).toThrow('maximumResultBytes must be a positive safe integer.'); + expect(() => createSelectedTabPublisher(options)).toThrow('maximumResultBytes must be a positive safe integer.'); + }); + + it('preserves supported extension URL schemes and rejects web documents', () => { + expect.assertions(3); + for (const protocol of ['chrome-extension:', 'moz-extension:']) { + const configuration = { extensionId: 'extension', allowedDocumentUrls: [`${protocol}//extension/popup.html`] }; + expect(defineApprovalSender(configuration)).toBe(configuration); + } + expect(() => defineApprovalSender({ extensionId: 'extension', allowedDocumentUrls: ['https://example.com'] })).toThrow('Approval documents must be extension URLs.'); + }); +}); diff --git a/packages/extension/test/notifications-headless.test.ts b/packages/extension/test/notifications-headless.test.ts index 8ccea6f..0bc05a4 100644 --- a/packages/extension/test/notifications-headless.test.ts +++ b/packages/extension/test/notifications-headless.test.ts @@ -7,7 +7,7 @@ it('runs without a document, window or theme listeners', () => { vi.stubGlobal('window', undefined); vi.stubGlobal('document', undefined); const subscribe = vi.fn(); - const controller = createBrowserControlNotificationController({ onReview: async () => {}, onRevoke: async () => {} }); + const controller = createBrowserControlNotificationController({ onReview: async () => {}, onReject: async () => {}, onRevoke: async () => {} }); const unsubscribe = controller.subscribe(subscribe); controller.update({ requests: [], grants: [] }); expect(controller.snapshot()).toEqual({ requests: [], grants: [] }); diff --git a/packages/extension/test/notifications.test.ts b/packages/extension/test/notifications.test.ts index aa78fff..3ac493c 100644 --- a/packages/extension/test/notifications.test.ts +++ b/packages/extension/test/notifications.test.ts @@ -12,17 +12,21 @@ const request: BrokerRequest = { id: 'request', principalId: 'principal', princi const state: BrokerState = { revision: 1, providers: [], principals: [], requests: [request], grants: [], scopes: [], targets: [], leases: [] }; it('keeps dismissal local and expires requests without another broker update', async () => { - expect.assertions(5); + expect.assertions(7); vi.useFakeTimers(); vi.setSystemTime(1_000); const review = vi.fn(); + const reject = vi.fn(); const revoke = vi.fn(); - const controller = createBrowserControlNotificationController({ onReview: review, onRevoke: revoke }); + const controller = createBrowserControlNotificationController({ onReview: review, onReject: reject, onRevoke: revoke }); controller.update(state); await controller.review(request.id); expect(review).toHaveBeenCalledWith(request); controller.dismiss(request.id); expect(controller.snapshot().requests).toEqual([]); + controller.update({ ...state, requests: [{ ...request, principalLabel: 'Updated agent' }] }); + expect(controller.snapshot().requests).toEqual([]); + expect(reject).not.toHaveBeenCalled(); expect(revoke).not.toHaveBeenCalled(); controller.update({ ...state, requests: [] }); controller.update(state); @@ -31,3 +35,18 @@ it('keeps dismissal local and expires requests without another broker update', a expect(controller.snapshot().requests).toEqual([]); controller.dispose(); }); + +it('rejects a pending request authoritatively, distinct from local dismissal', async () => { + expect.assertions(3); + const pendingRequest = { ...request, expiresAt: null }; + const pendingState = { ...state, requests: [pendingRequest] }; + const reject = vi.fn(async () => {}); + const controller = createBrowserControlNotificationController({ onReview: vi.fn(), onReject: reject, onRevoke: vi.fn() }); + controller.update(pendingState); + await controller.reject(pendingRequest.id); + expect(reject).toHaveBeenCalledWith(pendingRequest.id); + controller.update({ ...pendingState, requests: [] }); + await expect(controller.reject(pendingRequest.id)).rejects.toThrow('no longer pending'); + expect(reject).toHaveBeenCalledOnce(); + controller.dispose(); +}); diff --git a/packages/extension/test/scaffold.test.ts b/packages/extension/test/scaffold.test.ts index 0418f14..4d61e1d 100644 --- a/packages/extension/test/scaffold.test.ts +++ b/packages/extension/test/scaffold.test.ts @@ -6,7 +6,7 @@ import * as testingModule from '../src/testing.js'; it('keeps the extension public entries importable', () => { expect.assertions(3); - expect(Object.keys(indexModule)).toEqual(['sendAgentHeartbeat', 'createAgentRecovery', 'createApprovalChannel', 'createExtensionApprovalSenderValidator', 'createBirpcAgentBootstrap', 'createBirpcOfferContentRelay', 'installBirpcOfferRuntimeHandler', 'parseBirpcConnectionOffer', 'createBrokerTabAssignment', 'createChildSessionRouter', 'createIndexedDbPairingStore', 'createSelectedTabLifecycle', 'createSelectedTabPublisher', 'createTabScopeLifecycle', 'createTabScopeManager', 'matchesTabScope', 'parseTabScopeSelector', 'createPageRequestBridge']); - expect(Object.keys(bootstrapModule)).toEqual(['parseBirpcConnectionOffer', 'createBirpcOfferContentRelay', 'createBirpcAgentBootstrap', 'installBirpcOfferRuntimeHandler']); + expect(Object.keys(indexModule)).toEqual(['sendAgentHeartbeat', 'defineRecovery', 'createAgentRecovery', 'defineApproval', 'defineApprovalSender', 'createApprovalChannel', 'createExtensionApprovalSenderValidator', 'defineBootstrap', 'defineOfferRelay', 'createBirpcAgentBootstrap', 'createBirpcOfferContentRelay', 'installBirpcOfferRuntimeHandler', 'parseBirpcConnectionOffer', 'createBrokerTabAssignment', 'createChildSessionRouter', 'definePairingStore', 'createIndexedDbPairingStore', 'defineSelectedTab', 'createSelectedTabLifecycle', 'definePublisher', 'createSelectedTabPublisher', 'defineTabLifecycle', 'createTabScopeLifecycle', 'defineTabScope', 'createTabScopeManager', 'matchesTabScope', 'parseTabScopeSelector', 'definePageRequest', 'createPageRequestBridge']); + expect(Object.keys(bootstrapModule)).toEqual(['parseBirpcConnectionOffer', 'createBirpcOfferContentRelay', 'createBirpcAgentBootstrap', 'installBirpcOfferRuntimeHandler', 'defineBootstrap', 'defineOfferRelay']); expect(Object.keys(testingModule)).toEqual([]); }); diff --git a/packages/extension/test/webmcp-session.test.ts b/packages/extension/test/webmcp-session.test.ts new file mode 100644 index 0000000..55b9ca0 --- /dev/null +++ b/packages/extension/test/webmcp-session.test.ts @@ -0,0 +1,199 @@ +import type { CapabilityGrant, JsonObject } from '@dvcol/cdb'; + +import type { SelectedTabPublisher } from '../src/index.js'; + +import { Buffer } from 'node:buffer'; + +import { createChromeDebuggerBridgeClient, createClientFacadeAdapter, createTargetBroker, webMcpMethods } from '@dvcol/cdb'; +import { createCdbToolSession } from '@dvcol/cdb-mcp'; +import { afterEach, expect, it, vi } from 'vitest'; + +import { createSelectedTabPublisher } from '../src/index.js'; + +const disposals: (() => Promise)[] = []; +afterEach(async () => { + for (const dispose of disposals.splice(0)) await dispose(); +}); + +async function harness(level: CapabilityGrant['level'] = 'interact', maximumInlineResultBytes = 65_536) { + const broker = createTargetBroker({ maximumInlineResultBytes }); + const state = { output: 'done', unsupported: false, complete: true, loaderId: 'first' }; + const dispatched = Promise.withResolvers(); + let publisher: SelectedTabPublisher; + const sendCommand = vi.fn(async (_target: unknown, method: string): Promise => { + if (method === 'Page.getFrameTree') return { frameTree: { frame: { id: 'main', loaderId: state.loaderId, url: 'https://example.test/' } } }; + if (method === 'WebMCP.enable') { + if (state.unsupported) throw new Error('Method not found'); + publisher.debuggerEvent({ tabId: 42 }, 'WebMCP.toolsAdded', { tools: [{ frameId: 'main', name: 'hidden', description: 'Private catalogue item', annotations: { readOnly: true } }] }); + } + if (method === 'WebMCP.invokeTool') { + dispatched.resolve(); + if (state.complete) publisher.debuggerEvent({ tabId: 42 }, 'WebMCP.toolResponded', { invocationId: 'native-invocation', status: 'Completed', output: state.output }); + return { invocationId: 'native-invocation' }; + } + return {}; + }); + publisher = createSelectedTabPublisher({ + capabilities: { level: 'unsafe' }, + chromeDebugger: { attach() {}, detach() {}, sendCommand }, + scopeId: crypto.randomUUID(), + webMcp: { discovery: { exclude: ['hidden'] } }, + publishTarget: target => broker.publishTarget(target), + updateTarget: target => broker.updateTarget(target), + revokeTarget: target => broker.revokeTarget(target.id, target.generation), + registerTargetExecutor: (target, executor) => broker.registerTargetExecutor(target, executor), + }); + const target = await publisher.publish({ tabId: 42, incognito: false, url: 'https://example.test/' }); + function principal(capabilityLevel = level) { + const authority = { connectionId: crypto.randomUUID(), principalId: crypto.randomUUID(), targetGrants: [{ bindingId: crypto.randomUUID(), capabilities: { level: capabilityLevel }, targetId: target.id, targetGeneration: target.generation }] }; + broker.connectClient(authority); + const client = { + ...createChromeDebuggerBridgeClient(createClientFacadeAdapter({ + acquireLease: request => broker.acquireLease(request, authority), + executeCommand: async command => broker.executeCommand(command, authority), + listTargets: () => broker.listTargets(authority), + releaseLease: request => broker.releaseLease(request, authority), + renewLease: request => broker.renewLease(request, authority), + readArtifact: request => broker.readArtifact(request, authority), + releaseArtifact: request => broker.releaseArtifact(request, authority), + subscribe: async request => broker.subscribe(request, authority), + watchTargets: () => broker.watchTargets(authority), + })), + async cancelCommand(request: { operationId: string }) { + broker.cancelCommand(request.operationId, authority); + }, + }; + const session = createCdbToolSession({ client }); + session.projectTarget(target); + disposals.push(async () => { + session.dispose(); + broker.disconnectClient(authority); + }); + function definition(name: string) { + const tool = session.definitions.find(candidate => candidate.name === name); + if (tool === undefined) throw new Error(`Missing ${name}`); + return tool; + } + async function invoke(name: string, input: Record = {}, signal = new AbortController().signal) { + const result = await definition(name).invoke({ targetRef: 't1', ...input }, { signal }); + const content = result.content[0]; + if (content?.type !== 'text') throw new Error('Expected text'); + return { error: result.isError, value: JSON.parse(content.text) as Record }; + } + return { client, session, invoke, definition, authority }; + } + disposals.push(async () => { + await publisher.revoke(); + broker.dispose(); + }); + return { ...principal(), principal, broker, publisher, target, state, sendCommand, dispatched: dispatched.promise }; +} + +it('keeps native discovery and direct invocation behind the granted access level', async () => { + expect.assertions(6); + const fixture = await harness('inspect'); + expect((await fixture.invoke('browser.list_webmcp_tools')).value).toMatchObject({ tools: [], enabled: true }); + expect((await fixture.invoke('browser.invoke_webmcp_tools', { toolName: 'hidden', input: {} })).error).toBe(true); + const actor = fixture.principal('interact'); + expect((await actor.invoke('browser.invoke_webmcp_tools', { toolName: 'hidden', input: {} })).value).toEqual({ status: 'completed', output: 'done' }); + expect(fixture.sendCommand.mock.calls.filter(([, method]) => method === 'WebMCP.invokeTool')).toHaveLength(1); + expect(fixture.sendCommand.mock.calls.some(([, method]) => method === 'Runtime.evaluate')).toBe(false); + expect(fixture.broker.listLeases(actor.authority)).toHaveLength(0); +}); + +it('reserves native commands even at unsafe access and requires an exclusive invocation lease', async () => { + expect.assertions(3); + const fixture = await harness('unsafe'); + const request = { durationMilliseconds: 30_000, targetId: fixture.target.id, targetGeneration: fixture.target.generation }; + await expect(fixture.client.acquireLease({ ...request, requestedMethods: ['WebMCP.invokeTool'] })).rejects.toMatchObject({ code: 'CAPABILITY_DENIED' }); + await expect(fixture.client.acquireLease({ ...request, requestedMethods: [webMcpMethods.invoke], mode: 'shared-read' })).rejects.toMatchObject({ code: 'CAPABILITY_DENIED' }); + const lease = await fixture.client.acquireLease({ ...request, requestedMethods: [webMcpMethods.invoke], mode: 'exclusive-control' }); + expect((await fixture.principal().invoke('browser.invoke_webmcp_tools', { toolName: 'hidden', input: {} })).error).toBe(true); + await fixture.client.releaseLease({ ...request, leaseId: lease.id }); +}); + +it('retains bounded artifacts for the owning semantic session and releases their leases', async () => { + expect.assertions(7); + const fixture = await harness('interact', 256); + fixture.state.output = 'large result '.repeat(100); + const result = await fixture.invoke('browser.invoke_webmcp_tools', { toolName: 'hidden', input: {} }); + expect(result.error).toBeUndefined(); + expect(result.value).toMatchObject({ artifact: { mediaType: 'application/json' }, targetRef: 't1' }); + expect(JSON.stringify(result.value)).not.toMatch(/targetGeneration|targetId/); + const artifact = result.value.artifact as { id: string }; + const request = { artifactId: artifact.id, leaseId: String(result.value.leaseId) }; + expect((await fixture.principal().invoke('browser.read_artifact', request)).error).toBe(true); + const read = await fixture.invoke('browser.read_artifact', request); + const payload: unknown = JSON.parse(Buffer.from(String(read.value.bytes), 'base64').toString()); + expect(payload).toMatchObject({ output: fixture.state.output }); + expect((await fixture.invoke('browser.release_artifact', request)).value).toEqual({ released: true }); + expect(fixture.broker.listLeases(fixture.authority)).toHaveLength(0); +}); + +it('drops retained artifacts on session disposal', async () => { + expect.assertions(2); + const fixture = await harness('interact', 256); + fixture.state.output = 'x'.repeat(1_000); + await fixture.invoke('browser.invoke_webmcp_tools', { toolName: 'hidden', input: {} }); + expect(fixture.broker.listLeases(fixture.authority)).toHaveLength(1); + fixture.session.dispose(); + expect(fixture.broker.listLeases(fixture.authority)).toHaveLength(0); +}); + +it('preserves feature-unsupported failures and rejects frame selection before dispatch', async () => { + expect.assertions(3); + const fixture = await harness(); + fixture.state.unsupported = true; + expect((await fixture.invoke('browser.list_webmcp_tools')).value).toMatchObject({ code: 'FEATURE_UNSUPPORTED' }); + await expect(fixture.invoke('browser.list_webmcp_tools', { frameId: 'child' })).rejects.toThrow(); + expect(fixture.sendCommand.mock.calls.some(([, method]) => method === 'WebMCP.invokeTool')).toBe(false); +}); + +it('reports cancellation after dispatch as unknown outcome without replaying the tool', async () => { + expect.assertions(3); + const fixture = await harness(); + fixture.state.complete = false; + const cancellation = new AbortController(); + const invoking = fixture.invoke('browser.invoke_webmcp_tools', { toolName: 'hidden', input: {} }, cancellation.signal); + await fixture.dispatched; + cancellation.abort(); + expect((await invoking).value).toMatchObject({ code: 'WEBMCP_OUTCOME_UNKNOWN' }); + expect(fixture.sendCommand.mock.calls.filter(([, method]) => method === 'WebMCP.invokeTool')).toHaveLength(1); + expect(fixture.broker.listLeases(fixture.authority)).toHaveLength(0); +}); + +it('exposes the typed client methods through the same authorized broker command path', async () => { + expect.assertions(2); + const fixture = await harness(); + const authority = { targetId: fixture.target.id, targetGeneration: fixture.target.generation }; + const listingLease = await fixture.client.acquireLease({ ...authority, requestedMethods: [webMcpMethods.list], durationMilliseconds: 30_000 }); + expect((await fixture.client.listWebMcpTools({ ...authority, leaseId: listingLease.id, operationId: crypto.randomUUID() })).value).toMatchObject({ tools: [], enabled: true }); + await fixture.client.releaseLease({ ...authority, leaseId: listingLease.id }); + const invocationLease = await fixture.client.acquireLease({ ...authority, requestedMethods: [webMcpMethods.invoke], durationMilliseconds: 30_000, mode: 'exclusive-control' }); + expect((await fixture.client.invokeWebMcpTools({ ...authority, leaseId: invocationLease.id, operationId: crypto.randomUUID(), toolName: 'hidden', input: {} })).value).toEqual({ status: 'completed', output: 'done' }); + await fixture.client.releaseLease({ ...authority, leaseId: invocationLease.id }); +}); + +it('fences a late invocation response after replacing the session client', async () => { + expect.assertions(2); + const fixture = await harness(); + fixture.state.complete = false; + const invoking = fixture.invoke('browser.invoke_webmcp_tools', { toolName: 'hidden', input: {} }); + await fixture.dispatched; + fixture.session.rebindClient(fixture.client); + fixture.publisher.debuggerEvent({ tabId: 42 }, 'WebMCP.toolResponded', { invocationId: 'native-invocation', status: 'Completed', output: 'late' }); + expect((await invoking).value).toMatchObject({ code: 'WEBMCP_OUTCOME_UNKNOWN' }); + expect(fixture.broker.listLeases(fixture.authority)).toHaveLength(0); +}); + +it('reports a lost provider response after dispatch without replaying an invocation', async () => { + expect.assertions(3); + const fixture = await harness(); + const execute = vi.fn(async () => { + throw new Error('Provider transport closed after send'); + }); + fixture.broker.registerTargetExecutor(fixture.target, { execute }); + expect((await fixture.invoke('browser.invoke_webmcp_tools', { toolName: 'hidden', input: {} })).value).toMatchObject({ code: 'WEBMCP_OUTCOME_UNKNOWN', retryable: false }); + expect(execute).toHaveBeenCalledTimes(1); + expect(fixture.broker.listLeases(fixture.authority)).toHaveLength(0); +}); diff --git a/packages/extension/test/webmcp.test.ts b/packages/extension/test/webmcp.test.ts new file mode 100644 index 0000000..c1cd506 --- /dev/null +++ b/packages/extension/test/webmcp.test.ts @@ -0,0 +1,217 @@ +import type { JsonObject, WebMcpToolList } from '@dvcol/cdb'; + +import type { WebMcpController, WebMcpOptions } from '../src/webmcp.js'; + +import { webMcpMethods } from '@dvcol/cdb'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { createWebMcpController, validateWebMcpOptions } from '../src/webmcp.js'; + +const disposals: (() => Promise)[] = []; +afterEach(async () => { + for (const dispose of disposals.splice(0)) await dispose(); +}); + +function harness(policy?: WebMcpOptions, maximumResultBytes = 16_777_216) { + const state = { + tools: [{ name: 'search', description: 'Search products', frameId: 'main', inputSchema: { type: 'object' }, annotations: { readOnly: true } }] as JsonObject[], + loaderId: 'document-1', + unsupported: false, + completeImmediately: true, + output: null as import('@dvcol/cdb').JsonValue, + lastInvocation: '', + }; + const dispatched = Promise.withResolvers(); + let controller: WebMcpController; + const sendCommand = vi.fn(async (method: string, _parameters?: JsonObject): Promise => { + if (method === 'Page.getFrameTree') return { frameTree: { frame: { id: 'main', loaderId: state.loaderId, url: 'https://example.com/' } } }; + if (method === 'WebMCP.enable') { + if (state.unsupported) throw new Error('Method not found'); + if (state.tools.length > 0) controller.event('WebMCP.toolsAdded', { tools: state.tools }); + } + if (method === 'WebMCP.invokeTool') { + state.lastInvocation = crypto.randomUUID(); + dispatched.resolve(); + if (state.completeImmediately) controller.event('WebMCP.toolResponded', { invocationId: state.lastInvocation, status: 'Completed', output: state.output }); + return { invocationId: state.lastInvocation }; + } + return {}; + }); + controller = createWebMcpController({ target: { id: crypto.randomUUID(), generation: 1 }, maximumResultBytes, sendCommand, ...(policy === undefined ? {} : { policy }) }); + disposals.push(controller.dispose); + return { + controller, + dispatched: dispatched.promise, + sendCommand, + state, + list: async (signal = new AbortController().signal) => controller.execute(webMcpMethods.list, {}, signal) as unknown as Promise, + invoke: async (parameters: JsonObject, signal = new AbortController().signal) => controller.execute(webMcpMethods.invoke, parameters, signal), + }; +} + +describe('native main-document WebMCP', () => { + it('lists native descriptors while excluding same-process iframe registrations', async () => { + expect.assertions(4); + const fixture = harness(); + fixture.state.tools.push({ name: 'frame_tool', frameId: 'child', description: 'iframe' }); + const result = await fixture.list(); + expect(result.tools).toHaveLength(1); + expect(result.tools[0]).toMatchObject({ name: 'search', inputSchema: { type: 'object' }, annotations: { readOnly: true } }); + expect(result.tools[0]).not.toHaveProperty('frameId'); + expect(fixture.sendCommand.mock.calls.map(([method]) => method)).toEqual(['Page.getFrameTree', 'WebMCP.enable', 'Page.getFrameTree']); + }); + + it('completes an empty catalogue without waiting for a registration event', async () => { + expect.assertions(2); + const fixture = harness(); + fixture.state.tools = []; + expect(await fixture.list()).toMatchObject({ tools: [], enabled: true }); + fixture.state.unsupported = true; + await fixture.controller.setActive(false); + await expect(fixture.list()).rejects.toMatchObject({ code: 'FEATURE_UNSUPPORTED', retryable: false }); + }); + + it('reevaluates inclusion and exclusion callbacks without making hidden tools uncallable', async () => { + expect.assertions(4); + let hidden = true; + const fixture = harness({ discovery: { include: ['search'], exclude: [({ tool }) => hidden && tool.inputSchema?.type === 'object'] } }); + expect((await fixture.list()).tools).toEqual([]); + expect(await fixture.invoke({ toolName: 'search', input: {} })).toEqual({ status: 'completed', output: null }); + hidden = false; + const listed = await fixture.list(); + expect(listed.tools).toHaveLength(1); + fixture.state.output = 'plain text'; + expect(await fixture.invoke({ toolRef: listed.tools[0]!.toolRef, input: {} })).toEqual({ status: 'completed', output: 'plain text' }); + }); + + it('does not mutate stateful regexes and lets exclusions win', async () => { + expect.assertions(3); + const pattern = /search/g; + pattern.lastIndex = 3; + const fixture = harness({ discovery: { include: [pattern], exclude: [/search/u] } }); + expect((await fixture.list()).tools).toEqual([]); + expect((await fixture.list()).tools).toEqual([]); + expect(pattern.lastIndex).toBe(3); + }); + + it('keeps disabled discovery separate from invocation and surfaces callback failures', async () => { + expect.assertions(4); + const fixture = harness({ discovery: { enabled: false } }); + expect(await fixture.list()).toMatchObject({ tools: [], enabled: false }); + expect(await fixture.invoke({ toolName: 'search', input: {} })).toMatchObject({ status: 'completed' }); + const failing = harness({ discovery: { enabled: () => { + throw new Error('host policy unavailable'); + } } }); + await expect(failing.list()).rejects.toMatchObject({ code: 'WEBMCP_DISCOVERY_FAILED' }); + expect(() => validateWebMcpOptions({ discovery: { exclude: [123] } } as unknown as WebMcpOptions)).toThrow(TypeError); + }); + + it('rejects old document references even without a navigation event', async () => { + expect.assertions(3); + const fixture = harness(); + const first = await fixture.list(); + fixture.state.loaderId = 'document-2'; + await expect(fixture.invoke({ toolRef: first.tools[0]!.toolRef, input: {} })).rejects.toMatchObject({ code: 'WEBMCP_TOOL_STALE', retryable: true }); + expect(fixture.sendCommand.mock.calls.some(([method]) => method === 'WebMCP.invokeTool')).toBe(false); + expect((await fixture.list()).documentRef).not.toBe(first.documentRef); + }); + + it('retains document references while domain demand is released and reacquired', async () => { + expect.assertions(2); + const fixture = harness(); + const first = await fixture.list(); + await fixture.controller.setActive(false); + expect((await fixture.list()).tools[0]!.toolRef).toBe(first.tools[0]!.toolRef); + fixture.controller.event('WebMCP.toolsRemoved', { tools: [{ name: 'search', frameId: 'main' }] }); + await expect(fixture.invoke({ toolRef: first.tools[0]!.toolRef, input: {} })).rejects.toMatchObject({ code: 'WEBMCP_TOOL_STALE' }); + }); + + it('correlates completion and ignores another invocation result', async () => { + expect.assertions(2); + const fixture = harness(); + fixture.state.completeImmediately = false; + const invocation = fixture.invoke({ toolName: 'search', input: {} }); + await fixture.dispatched; + fixture.controller.event('WebMCP.toolResponded', { invocationId: 'foreign', status: 'Completed', output: 'foreign' }); + fixture.controller.event('WebMCP.toolResponded', { invocationId: fixture.state.lastInvocation, status: 'Completed', output: [1, true, null] }); + expect(await invocation).toEqual({ status: 'completed', output: [1, true, null] }); + expect(fixture.sendCommand.mock.calls.filter(([method]) => method === 'WebMCP.invokeTool')).toHaveLength(1); + }); + + it('cancels a dispatched tool without claiming its effects were rolled back', async () => { + expect.assertions(3); + const fixture = harness(); + fixture.state.completeImmediately = false; + const cancellation = new AbortController(); + const invocation = fixture.invoke({ toolName: 'search', input: {} }, cancellation.signal); + const rejected = expect(invocation).rejects.toMatchObject({ code: 'WEBMCP_OUTCOME_UNKNOWN', retryable: false }); + await fixture.dispatched; + cancellation.abort(); + await rejected; + expect(fixture.sendCommand).toHaveBeenCalledWith('WebMCP.cancelInvocation', { invocationId: fixture.state.lastInvocation }); + expect(fixture.sendCommand.mock.calls.filter(([method]) => method === 'WebMCP.invokeTool')).toHaveLength(1); + }); + + it('fences a result when the main document navigates during execution', async () => { + expect.assertions(2); + const fixture = harness(); + fixture.state.completeImmediately = false; + const invocation = fixture.invoke({ toolName: 'search', input: {} }); + const rejected = expect(invocation).rejects.toMatchObject({ code: 'WEBMCP_OUTCOME_UNKNOWN' }); + await fixture.dispatched; + fixture.controller.event('Page.frameNavigated', { frame: { id: 'main', loaderId: 'new-document' } }); + await rejected; + expect(fixture.sendCommand).toHaveBeenCalledWith('WebMCP.cancelInvocation', { invocationId: fixture.state.lastInvocation }); + }); + + it('cancels waiting on a host callback and rejects incomplete native catalogues', async () => { + expect.assertions(2); + const callbackStarted = Promise.withResolvers(); + const callbackResult = Promise.withResolvers(); + const fixture = harness({ discovery: { enabled: async () => { + callbackStarted.resolve(); + return callbackResult.promise; + } } }); + const cancellation = new AbortController(); + const listing = fixture.list(cancellation.signal); + const rejected = expect(listing).rejects.toMatchObject({ code: 'REQUEST_CANCELLED' }); + await callbackStarted.promise; + cancellation.abort(); + await rejected; + callbackResult.resolve(true); + const bounded = harness(undefined, 8); + await expect(bounded.list()).rejects.toMatchObject({ code: 'WEBMCP_DISCOVERY_FAILED' }); + }); + it('rejects malformed selectors and oversized early completions without dispatch replay', async () => { + expect.assertions(5); + const fixture = harness(undefined, 512); + await expect(fixture.invoke({ toolName: 'search', toolRef: 123, input: {} })).rejects.toMatchObject({ code: 'CDP_COMMAND_FAILED' }); + await expect(fixture.invoke({ toolName: 'search', frameId: 'child', input: {} })).rejects.toMatchObject({ code: 'CDP_COMMAND_FAILED' }); + expect(fixture.sendCommand.mock.calls.filter(([method]) => method === 'WebMCP.invokeTool')).toHaveLength(0); + fixture.state.output = 'x'.repeat(1_000); + await expect(fixture.invoke({ toolName: 'search', input: {} })).rejects.toMatchObject({ code: 'WEBMCP_OUTCOME_UNKNOWN' }); + expect(fixture.sendCommand.mock.calls.filter(([method]) => method === 'WebMCP.invokeTool')).toHaveLength(1); + }); + + it('does not let policy callbacks mutate native schema metadata', async () => { + expect.assertions(1); + let mutate = true; + const fixture = harness({ discovery: { exclude: [({ tool }) => { + if (mutate && tool.inputSchema !== undefined) tool.inputSchema.type = 'string'; + return false; + }] } }); + await fixture.list(); + mutate = false; + expect((await fixture.list()).tools[0]?.inputSchema).toEqual({ type: 'object' }); + }); + it('does not claim a native transport rejection means that no side effect occurred', async () => { + expect.assertions(1); + const fixture = harness(); + const nativePort = fixture.sendCommand.getMockImplementation()!; + fixture.sendCommand.mockImplementation(async (method, parameters) => { + if (method === 'WebMCP.invokeTool') throw new Error('Debugger transport closed after send'); + return nativePort(method, parameters); + }); + await expect(fixture.invoke({ toolName: 'search', input: {} })).rejects.toMatchObject({ code: 'WEBMCP_OUTCOME_UNKNOWN', retryable: false }); + }); +}); diff --git a/packages/mcp/README.md b/packages/mcp/README.md index b1b1e2c..0d70de5 100644 --- a/packages/mcp/README.md +++ b/packages/mcp/README.md @@ -13,3 +13,13 @@ import { createCdbToolSession } from '@dvcol/cdb-mcp'; Create one tool session per authenticated principal and dispose it with that principal. See the [repository documentation](https://github.com/dvcol/chrome-debugger-bridge#readme) for the authorization model. Opt-in Node diagnostic channels keep execution details outside agent responses. `cdb.mcp.action` reports action phases, CDP command outcomes and dispatch boundaries. `cdb.mcp.snapshot` separates frame discovery, command round trips, artifact reads and formatting; command round trips include provider and transport time. `cdb.mcp.hit-test` reports element and ancestor-frame geometry and hit nodes for diagnosing coordinate mismatches. Treat hit-test records as private page diagnostics. + +## Page WebMCP + +CDB exposes native main-document WebMCP through `browser.list_webmcp_tools` and +`browser.invoke_webmcp_tools`. Discovery requires inspect access; invocation requires +interact access and an exclusive lease. Extension hosts can configure discovery with +`defineProvider({ webMcp: { discovery: { include, exclude, enabled } }, ... })`. +Filters hide tools from discovery; they do not block direct invocation by name. +See [native page WebMCP](../../docs/webmcp.md) for references, artifacts, cancellation, +and configuration examples. diff --git a/packages/mcp/src/index.ts b/packages/mcp/src/index.ts index 7ef6ce5..750bc17 100644 --- a/packages/mcp/src/index.ts +++ b/packages/mcp/src/index.ts @@ -30,6 +30,7 @@ import { Buffer } from 'node:buffer'; import { randomUUID } from 'node:crypto'; import { channel } from 'node:diagnostics_channel'; +import { createWebMcpClient, webMcpMethods } from '@dvcol/cdb'; import { createAgentSession } from '@dvcol/cdb/agent'; import { requiredLeaseMode } from '@dvcol/cdb/cdp-catalogue'; import { toNodeHandler } from '@modelcontextprotocol/node'; @@ -446,11 +447,12 @@ async function executeArtifactCommand( method: string, parameters: JsonObject, signal: AbortSignal, + mode: Lease['mode'] = 'shared-read', ): Promise { signal.throwIfAborted(); const lease = await client.acquireLease({ durationMilliseconds: 30_000, - mode: 'shared-read', + mode, requestedMethods: [method], targetGeneration: input.targetGeneration, targetId: input.targetId, @@ -476,6 +478,9 @@ async function executeArtifactCommand( targetGeneration: input.targetGeneration, targetId: input.targetId, }); + if (signal.aborted && method === webMcpMethods.invoke) + throw new McpToolError('WEBMCP_OUTCOME_UNKNOWN', 'The invocation was interrupted after dispatch. Do not replay it.'); + signal.throwIfAborted(); const artifact = artifactFromCommandResult(result); if (artifact === undefined) return typeof result === 'object' && result !== null && 'value' in result @@ -484,6 +489,7 @@ async function executeArtifactCommand( retainLease = true; return { artifact, lease }; } catch (error) { + if (signal.aborted && method === webMcpMethods.invoke) throw new McpToolError('WEBMCP_OUTCOME_UNKNOWN', 'The WebMCP invocation was interrupted. Do not replay it automatically.'); throw signal.aborted ? signal.reason : error; } finally { signal.removeEventListener('abort', abort); @@ -1386,8 +1392,10 @@ type ElementReference = NativeElementReference | ProviderElementReference; interface CdbToolSessionState { readonly agentSession: AgentSession; disposed: boolean; + connectionRevision: number; nextElementReference: number; readonly elementReferences: Map; + readonly retainedArtifacts: Map; } interface AccessibilityCandidate extends NativeElementReference { @@ -1463,7 +1471,9 @@ function createCdbToolSessionState(): CdbToolSessionState { return { agentSession: createAgentSession(), disposed: false, + connectionRevision: 0, elementReferences: new Map(), + retainedArtifacts: new Map(), nextElementReference: 1, }; } @@ -1474,6 +1484,7 @@ function createRebindableMcpClient(initialClient: McpChromeDebuggerBridgeClient) } { const holder: { current: McpChromeDebuggerBridgeClient } = { current: initialClient }; const client: McpChromeDebuggerBridgeClient = { + ...createWebMcpClient({ executeCommand: async command => client.executeCommand(command) }), acquireLease: async request => holder.current.acquireLease(request), ...(initialClient.cancelAutomation === undefined ? {} @@ -3132,10 +3143,61 @@ function createCdbToolDefinitionsForSession( }); }; const rawClient = options.client; + function artifactAuthorityShape(): { + artifactId: z.ZodString; + leaseId: z.ZodString; + targetRef: z.ZodString | z.ZodOptional; + targetId?: z.ZodOptional; + targetGeneration?: z.ZodOptional; + } { + const identity = { artifactId: z.string().uuid(), leaseId: z.string().uuid() }; + if (options.enableRawCdp) return { + ...identity, + targetRef: z.string().regex(targetReferencePattern).optional(), + targetId: z.string().uuid().optional(), + targetGeneration: z.number().int().nonnegative().optional(), + }; + return { ...identity, targetRef: z.string().regex(targetReferencePattern) }; + } + const artifactAuthoritySchema = artifactAuthorityShape(); + async function resolveArtifactAuthority(input: { + artifactId: string; + leaseId: string; + targetRef?: string | undefined; + targetId?: unknown; + targetGeneration?: unknown; + }): Promise<{ artifactId: string; leaseId: string; targetId: string; targetGeneration: number }> { + if (input.targetRef === undefined) { + if (!options.enableRawCdp || typeof input.targetId !== 'string' || typeof input.targetGeneration !== 'number') + throw new McpToolError('MCP_ARTIFACT_AUTHORITY_REQUIRED', 'Provide the targetRef and leaseId returned with the artifact.'); + return { artifactId: input.artifactId, leaseId: input.leaseId, targetId: input.targetId, targetGeneration: input.targetGeneration }; + } + if (input.targetId !== undefined || input.targetGeneration !== undefined) + throw new McpToolError('MCP_ARTIFACT_AUTHORITY_REQUIRED', 'Provide targetRef without raw target identity.'); + const target = await resolveSemanticTarget(rawClient, sessionState, input.targetRef); + const retained = sessionState.retainedArtifacts.get(input.artifactId); + if (retained === undefined || retained.lease.id !== input.leaseId || retained.lease.targetId !== target.id || retained.lease.targetGeneration !== target.generation) + throw new McpToolError('MCP_ARTIFACT_AUTHORITY_REQUIRED', 'The artifact does not belong to this target and tool session.'); + return { artifactId: input.artifactId, leaseId: input.leaseId, targetId: target.id, targetGeneration: target.generation }; + } + async function webMcpResult(result: unknown, targetRef: string, connectionRevision: number, invocation = false): Promise { + const retained = retainedArtifactResult(result); + if (sessionState.disposed || sessionState.connectionRevision !== connectionRevision) { + if (retained !== undefined) await rawClient.releaseLease({ leaseId: retained.lease.id, targetId: retained.lease.targetId, targetGeneration: retained.lease.targetGeneration }).catch(() => {}); + throw new McpToolError(invocation ? 'WEBMCP_OUTCOME_UNKNOWN' : 'WEBMCP_TOOL_STALE', 'The browser tool session changed while the operation was running.'); + } + if (retained === undefined) return result; + for (const [artifactId, entry] of sessionState.retainedArtifacts) { + if (Date.parse(entry.lease.expiresAt) <= Date.now()) sessionState.retainedArtifacts.delete(artifactId); + } + sessionState.retainedArtifacts.set(retained.artifact.id, retained); + return { artifact: retained.artifact, leaseId: retained.lease.id, expiresAt: retained.lease.expiresAt, targetRef }; + } const batchLease = new AsyncLocalStorage(); const cancelAutomation = rawClient.cancelAutomation; const executeAutomation = rawClient.executeAutomation; const client: McpChromeDebuggerBridgeClient = { + ...createWebMcpClient({ executeCommand: async command => client.executeCommand(command) }), async acquireLease(request) { const scope = inputActionScope.getStore(); scope?.signal.throwIfAborted(); @@ -3487,15 +3549,14 @@ function createCdbToolDefinitionsForSession( safety: 'action', description: 'Release an authorized artifact through its owning lease.', inputSchema: z.object({ - artifactId: z.string().uuid(), - targetGeneration: z.number().int().nonnegative(), - targetId: z.string().uuid(), - leaseId: z.string().uuid(), + ...artifactAuthoritySchema, }), }, async (input) => { try { - await rawClient.releaseArtifact(input); + const authority = await resolveArtifactAuthority(input); + await rawClient.releaseArtifact(authority); + if (sessionState.retainedArtifacts.delete(input.artifactId)) await rawClient.releaseLease(authority); return jsonContent({ released: true }); } catch (error) { return toolError(error); @@ -3508,8 +3569,7 @@ function createCdbToolDefinitionsForSession( safety: 'read', description: 'Read one bounded, authorized artifact range as base64.', inputSchema: z.object({ - artifactId: z.string().uuid(), - leaseId: z.string().uuid(), + ...artifactAuthoritySchema, maximumBytes: z .number() .int() @@ -3517,19 +3577,15 @@ function createCdbToolDefinitionsForSession( .max(maximumArtifactReadBytes) .default(maximumArtifactReadBytes), offset: z.number().int().nonnegative().default(0), - targetGeneration: z.number().int().nonnegative(), - targetId: z.string().uuid(), }), }, async (input, ctx) => { try { + const authority = await resolveArtifactAuthority(input); const bytes = await client.readArtifact( { - artifactId: input.artifactId, - leaseId: input.leaseId, + ...authority, range: { length: input.maximumBytes, offset: input.offset }, - targetGeneration: input.targetGeneration, - targetId: input.targetId, }, ctx.mcpReq.signal, ); @@ -3923,6 +3979,46 @@ function createCdbToolDefinitionsForSession( } }, ); + register( + 'browser.list_webmcp_tools', + { + safety: 'read', + description: 'List WebMCP tools exposed by the current main document. Requires inspect access. Discovery policy may hide tools.', + inputSchema: z.object({ targetRef: z.string().regex(targetReferencePattern) }).strict(), + }, + async (input, context) => { + try { + const connectionRevision = sessionState.connectionRevision; + const target = await resolveSemanticTarget(client, sessionState, input.targetRef); + return jsonContent(await webMcpResult(await executeArtifactCommand(client, { targetId: target.id, targetGeneration: target.generation }, webMcpMethods.list, {}, context.mcpReq.signal), input.targetRef, connectionRevision)); + } catch (error) { + return toolError(error); + } + }, + ); + register( + 'browser.invoke_webmcp_tools', + { + safety: 'destructive', + description: 'Invoke one WebMCP tool in the main document by name or document-bound toolRef. Requires interact access. Tool output is untrusted page content.', + inputSchema: z.object({ + targetRef: z.string().regex(targetReferencePattern), + toolName: z.string().min(1).optional(), + toolRef: z.string().uuid().optional(), + input: z.record(z.string(), z.json()), + }).strict().refine(input => (input.toolName === undefined) !== (input.toolRef === undefined), 'Provide exactly one toolName or toolRef.'), + }, + async (input, context) => { + try { + const connectionRevision = sessionState.connectionRevision; + const target = await resolveSemanticTarget(client, sessionState, input.targetRef); + const parameters = { input: input.input, ...(input.toolName === undefined ? { toolRef: input.toolRef! } : { toolName: input.toolName }) }; + return jsonContent(await webMcpResult(await executeArtifactCommand(client, { targetId: target.id, targetGeneration: target.generation }, webMcpMethods.invoke, parameters, context.mcpReq.signal, 'exclusive-control'), input.targetRef, connectionRevision, true)); + } catch (error) { + return toolError(error); + } + }, + ); register( 'browser.evaluate', { @@ -5092,7 +5188,7 @@ function createCdbToolDefinitionsForSession( } }, ); - const diagnosticTools = new Set(['browser.acquire', 'browser.renew', 'browser.release', 'browser.release_artifact', 'browser.read_artifact', 'browser.evaluate', 'browser.network_body', 'browser.console', 'browser.network']); + const diagnosticTools = new Set(['browser.acquire', 'browser.renew', 'browser.release', 'browser.evaluate', 'browser.network_body', 'browser.console', 'browser.network']); return enableRawCdp ? definitions : definitions.filter(definition => !diagnosticTools.has(definition.name)); } @@ -5100,24 +5196,37 @@ function createCdbToolDefinitionsForSession( export function createCdbToolSession( options: RegisterCdbToolsOptions, ): CdbToolSession { + defineTools(options); const state = createCdbToolSessionState(); const rebindableClient = createRebindableMcpClient(options.client); const definitions = createCdbToolDefinitionsForSession({ ...options, client: rebindableClient.client }, state); + function releaseRetainedArtifacts(targetId?: string): void { + for (const [artifactId, { lease }] of state.retainedArtifacts) { + if (targetId !== undefined && lease.targetId !== targetId) continue; + state.retainedArtifacts.delete(artifactId); + void rebindableClient.client.releaseLease({ leaseId: lease.id, targetId: lease.targetId, targetGeneration: lease.targetGeneration }).catch(() => {}); + } + } return { definitions, dispose() { + releaseRetainedArtifacts(); state.disposed = true; + state.connectionRevision += 1; state.agentSession.dispose(); state.elementReferences.clear(); }, projectTarget: target => projectSemanticTarget(state, target), rebindClient(client) { if (state.disposed) return; + releaseRetainedArtifacts(); + state.connectionRevision += 1; rebindableClient.rebind(client); state.elementReferences.clear(); }, revokeTarget(targetId) { if (state.disposed) return; + releaseRetainedArtifacts(targetId); state.agentSession.revoke(targetId); for (const [elementRef, element] of state.elementReferences) if (element.targetId === targetId) state.elementReferences.delete(elementRef); @@ -5188,6 +5297,7 @@ function createMcpServer( export function mountMcpStreamableHttp( options: MountMcpStreamableHttpOptions, ): MountedMcpStreamableHttp { + defineHttp(options); const path = options.path ?? '/cdb/mcp'; const session = createCdbToolSession(options); const handler = createMcpHandler( @@ -5221,6 +5331,7 @@ export function mountMcpStreamableHttp( /** Starts an optional stdio adapter around the same MCP tool surface without owning broker lifecycle. */ export function mountMcpStdio(options: MountMcpStdioOptions): MountedMcpStdio { + defineStdio(options); const session = createCdbToolSession(options); const mounted = serveStdio( () => createMcpServer(session.definitions), @@ -5233,3 +5344,21 @@ export function mountMcpStdio(options: MountMcpStdioOptions): MountedMcpStdio { }, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineTools(definition: Definition): Definition { + resolveMcpTimingPolicy(definition.timing); + return definition; +} + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineHttp(definition: Definition): Definition { + defineTools(definition); + return definition; +} + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineStdio(definition: Definition): Definition { + defineTools(definition); + return definition; +} diff --git a/packages/mcp/test/mvp-session.test.ts b/packages/mcp/test/mvp-session.test.ts index 6bf52ae..e316fc9 100644 --- a/packages/mcp/test/mvp-session.test.ts +++ b/packages/mcp/test/mvp-session.test.ts @@ -85,7 +85,7 @@ it('returns screenshot image content and releases its artifact without exposing expect(result.content).toEqual([{ type: 'image', data: imageData, mimeType: 'image/png' }]); expect(released).toContain('screenshot-artifact'); expect(released).toContain('60013aa6-2e2d-4fa1-9e10-8de7c0e811e3'); - expect(harness.session.definitions.some(definition => definition.name === 'browser.read_artifact')).toBe(false); + expect(harness.session.definitions.some(definition => definition.name === 'browser.raw_cdp')).toBe(false); }); it('finds a control below 200 structural ancestors without spending its output budget on indentation', async () => { @@ -371,7 +371,7 @@ it('keeps raw authority and debug execution out of the default compact catalogue const session = createCdbToolSession({ client: {} as McpChromeDebuggerBridgeClient }); const catalogue = session.definitions.map(({ name, description, inputSchema }) => ({ name, description, inputSchema })); - expect(catalogue.some(tool => ['browser.acquire', 'browser.raw_cdp', 'browser.evaluate', 'browser.read_artifact'].includes(tool.name))).toBe(false); + expect(catalogue.some(tool => ['browser.acquire', 'browser.raw_cdp', 'browser.evaluate'].includes(tool.name))).toBe(false); expect(JSON.stringify(catalogue).length).toBeLessThan(70_000); expect(catalogue.find(tool => tool.name === 'browser.batch')).toBeDefined(); }); diff --git a/packages/mcp/test/streamable-http.test.ts b/packages/mcp/test/streamable-http.test.ts index 62ac93b..ce0a747 100644 --- a/packages/mcp/test/streamable-http.test.ts +++ b/packages/mcp/test/streamable-http.test.ts @@ -590,6 +590,8 @@ it('serves target discovery through the official SDK Streamable HTTP client', as 'browser.snapshot', 'browser.screenshot', 'browser.network_body', + 'browser.list_webmcp_tools', + 'browser.invoke_webmcp_tools', 'browser.evaluate', 'browser.navigate', 'browser.back', diff --git a/packages/websocket/src/artifact-http.ts b/packages/websocket/src/artifact-http.ts index c0dea3b..a1093ff 100644 --- a/packages/websocket/src/artifact-http.ts +++ b/packages/websocket/src/artifact-http.ts @@ -49,15 +49,8 @@ function parseRange(value: string | undefined, length: number): { readonly end: } /** Mounts authenticated, range-capable artifact reads. Authorization stays in headers, never in artifact URLs. */ -export function mountAuthenticatedArtifactHttpEndpoint(input: { - readonly authenticate: ClientAuthenticationAdapter; - readonly originPolicy: (claims: TransportClaims, signal: AbortSignal) => boolean | Promise; - readonly path?: string; - readonly readArtifact: (artifactId: string, principal: Principal, signal: AbortSignal) => Promise<{ readonly bytes: Uint8Array; readonly descriptor: ArtifactDescriptor } | undefined>; - /** Releases only artifacts owned by the authenticated principal. Omit for a read-only endpoint. */ - readonly releaseArtifact?: (artifactId: string, principal: Principal, signal: AbortSignal) => Promise; - readonly server: import('node:http').Server; -}): MountedAuthenticatedArtifactHttpEndpoint { +export function mountAuthenticatedArtifactHttpEndpoint(input: ArtifactEndpointOptions): MountedAuthenticatedArtifactHttpEndpoint { + defineArtifactEndpoint(input); const path = input.path ?? defaultArtifactHttpPath; if (!path.startsWith('/') || !path.endsWith('/')) throw new Error('The artifact HTTP path must start and end with a slash.'); const listener = (request: IncomingMessage, response: ServerResponse): void => { @@ -119,3 +112,21 @@ export function mountAuthenticatedArtifactHttpEndpoint input.server.off('request', listener) }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineArtifactEndpoint = ArtifactEndpointOptions>(definition: Definition & ArtifactEndpointOptions): Definition { + const path = definition.path ?? defaultArtifactHttpPath; + if (!path.startsWith('/') || !path.endsWith('/')) throw new Error('The artifact HTTP path must start and end with a slash.'); + return definition; +} + +/** Configuration accepted by mountAuthenticatedArtifactHttpEndpoint and defineArtifactEndpoint. */ +export interface ArtifactEndpointOptions { + readonly authenticate: ClientAuthenticationAdapter; + readonly originPolicy: (claims: TransportClaims, signal: AbortSignal) => boolean | Promise; + readonly path?: string; + readonly readArtifact: (artifactId: string, principal: Principal, signal: AbortSignal) => Promise<{ readonly bytes: Uint8Array; readonly descriptor: ArtifactDescriptor } | undefined>; + /** Releases only artifacts owned by the authenticated principal. Omit for a read-only endpoint. */ + readonly releaseArtifact?: (artifactId: string, principal: Principal, signal: AbortSignal) => Promise; + readonly server: import('node:http').Server; +} diff --git a/packages/websocket/src/artifact-reader.ts b/packages/websocket/src/artifact-reader.ts index 48ae54c..51df81e 100644 --- a/packages/websocket/src/artifact-reader.ts +++ b/packages/websocket/src/artifact-reader.ts @@ -3,12 +3,8 @@ import type { ArtifactDescriptor, ArtifactReader } from '@dvcol/cdb'; import { createArtifactReader } from '@dvcol/cdb'; /** Creates a common artifact reader for an authenticated HTTP endpoint. */ -export function createHttpArtifactReader(input: { - readonly authorization: string; - readonly descriptor: ArtifactDescriptor; - readonly endpoint: string; - readonly fetch?: typeof globalThis.fetch; -}): ArtifactReader { +export function createHttpArtifactReader(input: ArtifactReaderOptions): ArtifactReader { + defineArtifactReader(input); const endpoint = new URL(input.endpoint); if (endpoint.username || endpoint.password || endpoint.search || endpoint.hash) throw new Error('The artifact endpoint must not contain credentials, query parameters, or fragments.'); return createArtifactReader(input.descriptor, async (signal) => { @@ -20,3 +16,18 @@ export function createHttpArtifactReader(input: { return new Uint8Array(await response.arrayBuffer()); }); } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineArtifactReader(definition: Definition): Definition { + const endpoint = new URL(definition.endpoint); + if (endpoint.username || endpoint.password || endpoint.search || endpoint.hash) throw new Error('The artifact endpoint must not contain credentials, query parameters, or fragments.'); + return definition; +} + +/** Configuration accepted by createHttpArtifactReader and defineArtifactReader. */ +export interface ArtifactReaderOptions { + readonly authorization: string; + readonly descriptor: ArtifactDescriptor; + readonly endpoint: string; + readonly fetch?: typeof globalThis.fetch; +} diff --git a/packages/websocket/src/browser.ts b/packages/websocket/src/browser.ts index 2941d84..c29fd74 100644 --- a/packages/websocket/src/browser.ts +++ b/packages/websocket/src/browser.ts @@ -349,6 +349,7 @@ function resolveOrigin(explicitOrigin: string | undefined): string { export async function connectAgentWebSocket( options: ConnectAgentWebSocketOptions, ): Promise { + defineAgentWebSocket(options); const timeoutMilliseconds = resolveBrowserWebSocketTimingPolicy(options.timing).handshakeTimeoutMilliseconds; const maximumPendingMessages = options.maximumPendingAuthenticatedMessages ?? maximumPendingAuthenticatedMessages; if (!Number.isSafeInteger(maximumPendingMessages) || maximumPendingMessages < 1) { @@ -652,6 +653,7 @@ function encodeAuthorizationSubprotocol(authorization: string): string { export async function connectBrowserClientWebSocket( options: ConnectBrowserClientWebSocketOptions, ): Promise { + defineClientWebSocket(options); const timeoutMilliseconds = resolveBrowserWebSocketTimingPolicy(options.timing).handshakeTimeoutMilliseconds; const endpointUrl = new URL(options.endpoint); validateWebSocketEndpointSecurity(endpointUrl); @@ -786,6 +788,7 @@ interface BrowserSubscriptionState { export async function createBrowserChromeDebuggerBridgeClient( options: CreateBrowserChromeDebuggerBridgeClientOptions, ): Promise { + defineBrowserClient(options); let connection = await connectBrowserClientWebSocket(options); const pendingRequests = new Map(); const targetChanges = createAsyncQueue(maximumPendingAuthenticatedMessages); @@ -1024,3 +1027,30 @@ export async function createBrowserChromeDebuggerBridgeClient( closed, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineAgentWebSocket(definition: Definition): Definition { + resolveBrowserWebSocketTimingPolicy(definition.timing); + const maximumPendingMessages = definition.maximumPendingAuthenticatedMessages ?? maximumPendingAuthenticatedMessages; + if (!Number.isSafeInteger(maximumPendingMessages) || maximumPendingMessages < 1) throw new Error('The maximum pending authenticated messages must be a positive integer'); + const endpoint = new URL(definition.endpoint); + validateWebSocketEndpointSecurity(endpoint); + if (endpoint.username || endpoint.password || endpoint.search || endpoint.hash) throw new Error('The agent WebSocket endpoint must not contain credentials, query parameters, or fragments'); + return definition; +} + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineClientWebSocket(definition: Definition): Definition { + resolveBrowserWebSocketTimingPolicy(definition.timing); + const endpoint = new URL(definition.endpoint); + validateWebSocketEndpointSecurity(endpoint); + if (endpoint.username || endpoint.password || endpoint.search || endpoint.hash) throw new Error('The client WebSocket endpoint must not contain credentials, query parameters, or fragments'); + if (definition.authorization.length === 0) throw new Error('Client authorization is required'); + return definition; +} + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineBrowserClient(definition: Definition): Definition { + defineClientWebSocket(definition); + return definition; +} diff --git a/packages/websocket/src/file-artifact-store.ts b/packages/websocket/src/file-artifact-store.ts index 71a2032..25f6dbb 100644 --- a/packages/websocket/src/file-artifact-store.ts +++ b/packages/websocket/src/file-artifact-store.ts @@ -75,6 +75,7 @@ async function digest(bytes: Uint8Array): Promise { /** Opens a crash-safe, filesystem-backed artifact store for Node hosts. */ export async function createFileArtifactStore(options: FileArtifactStoreOptions): Promise { + defineArtifactStore(options); assertLimit(options.maximumBytes, 'byte'); assertLimit(options.maximumBytesPerOwner, 'per-owner byte'); const now = options.now ?? Date.now; @@ -277,3 +278,10 @@ export async function createFileArtifactStore(options: FileArtifactStoreOptions) }, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineArtifactStore(definition: Definition): Definition { + assertLimit(definition.maximumBytes, 'byte'); + assertLimit(definition.maximumBytesPerOwner, 'per-owner byte'); + return definition; +} diff --git a/packages/websocket/src/node.ts b/packages/websocket/src/node.ts index 3639bc6..828bb06 100644 --- a/packages/websocket/src/node.ts +++ b/packages/websocket/src/node.ts @@ -37,7 +37,7 @@ import { randomInt, randomUUID } from 'node:crypto'; import { createServer } from 'node:http'; import { styleText } from 'node:util'; -import { connectAgentTargetBroker, connectClientTargetBroker, createChromeDebuggerBridgeClient, createClientFacadeAdapter, createTargetBroker } from '@dvcol/cdb'; +import { connectAgentTargetBroker, connectClientTargetBroker, createChromeDebuggerBridgeClient, createClientFacadeAdapter, createTargetBroker, defineTargetBroker } from '@dvcol/cdb'; import { agentAuthenticationMessageSchema, agentToBrokerMessageSchema, @@ -65,7 +65,9 @@ import { validateWebSocketEndpointSecurity, } from './protocols.js'; +export { defineArtifactEndpoint } from './artifact-http.js'; export { defaultArtifactHttpPath, mountAuthenticatedArtifactHttpEndpoint, type MountedAuthenticatedArtifactHttpEndpoint } from './artifact-http.js'; +export { defineArtifactReader } from './artifact-reader.js'; export { createHttpArtifactReader } from './artifact-reader.js'; export { type ArtifactTransferControl, @@ -75,6 +77,7 @@ export { encodeArtifactChunk, streamArtifact, } from './artifacts.js'; +export { defineArtifactStore } from './file-artifact-store.js'; export { createFileArtifactStore, type FileArtifactStore, type FileArtifactStoreOptions } from './file-artifact-store.js'; export const defaultAgentWebSocketPath = '/cdb/agent'; @@ -1025,6 +1028,7 @@ export function mountAuthenticatedWebSocketBridge< >( options: MountAuthenticatedWebSocketBridgeOptions, ): MountedAuthenticatedWebSocketBridge { + defineWebSocketBridge(options); const agentPath = options.agentPath ?? defaultAgentWebSocketPath; const clientPath = options.clientPath ?? defaultClientWebSocketPath; validatePath(agentPath); @@ -1217,11 +1221,9 @@ export async function createStandaloneAuthenticatedWebSocketBridge< AgentPrincipal extends AuthenticatedPrincipal, ClientPrincipal extends AuthenticatedPrincipal, >( - options: Omit, 'server'> & { - readonly host?: string; - readonly port?: number; - }, + options: StandaloneWebSocketOptions, ): Promise { + defineStandaloneWebSocket(options); const host = options.host ?? '127.0.0.1'; if (!isLoopbackHostname(host === '::1' ? '[::1]' : host)) { throw new Error('The standalone WebSocket broker must bind to a loopback host'); @@ -1282,6 +1284,7 @@ export interface ConnectNodeClientWebSocketOptions { } export async function connectNodeClientWebSocket(options: ConnectNodeClientWebSocketOptions): Promise { + defineClientWebSocket(options); const endpointUrl = new URL(options.endpoint); validateWebSocketEndpointSecurity(endpointUrl); if (endpointUrl.username || endpointUrl.password || endpointUrl.search || endpointUrl.hash) { @@ -1412,6 +1415,7 @@ function validateArtifactEndpoint(endpoint: string): URL { /** Creates the complete reconnecting public CDB client facade for a header-authenticated Node endpoint. */ export async function createNodeChromeDebuggerBridgeClient(options: CreateNodeChromeDebuggerBridgeClientOptions): Promise { + defineNodeClient(options); const artifactEndpoint = validateArtifactEndpoint(options.artifactEndpoint); let connection = await connectNodeClientWebSocket(options); const pendingRequests = new Map(); @@ -1715,6 +1719,7 @@ function getArtifactDescriptor(message: BrokerToClientMessage): ArtifactDescript export async function createStandaloneChromeDebuggerBridgeHost( options: CreateStandaloneChromeDebuggerBridgeHostOptions, ): Promise { + defineStandaloneHost(options); const host = options.host ?? '127.0.0.1'; const brokerId = randomUUID(); const pairingLifetimeMilliseconds = options.pairingLifetimeMilliseconds === undefined @@ -1904,3 +1909,70 @@ export async function createStandaloneChromeDebuggerBridgeHost( }, }; } + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineWebSocketBridge = MountAuthenticatedWebSocketBridgeOptions>(definition: Definition & MountAuthenticatedWebSocketBridgeOptions): Definition { + validateBridgeConfiguration(definition); + return definition; +} + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineStandaloneWebSocket, 'server'> & { + readonly host?: string; + readonly port?: number; +} = Omit, 'server'> & { + readonly host?: string; + readonly port?: number; +}>(definition: Definition & Omit, 'server'> & { + readonly host?: string; + readonly port?: number; +}): Definition { + const host = definition.host ?? '127.0.0.1'; + if (!isLoopbackHostname(host === '::1' ? '[::1]' : host)) throw new Error('The standalone WebSocket broker must bind to a loopback host'); + validateBridgeConfiguration(definition); + return definition; +} + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineClientWebSocket(definition: Definition): Definition { + resolveNodeWebSocketTimingPolicy(definition.timing); + const endpoint = new URL(definition.endpoint); + validateWebSocketEndpointSecurity(endpoint); + if (endpoint.username || endpoint.password || endpoint.search || endpoint.hash) throw new Error('The client WebSocket endpoint must not contain credentials, query parameters, or fragments'); + return definition; +} + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineNodeClient(definition: Definition): Definition { + defineClientWebSocket(definition); + validateArtifactEndpoint(definition.artifactEndpoint); + return definition; +} + +/** Defines configuration without starting the adapter or calling runtime dependencies. */ +export function defineStandaloneHost(definition: Definition): Definition { + defineTargetBroker(definition); + validateBridgeConfiguration({ timing: definition.webSocketTiming ?? {} }); + const pairingLifetimeMilliseconds = definition.pairingLifetimeMilliseconds === undefined ? 5 * 60_000 : definition.pairingLifetimeMilliseconds; + validateTimeoutMilliseconds(pairingLifetimeMilliseconds, 'pairingLifetimeMilliseconds'); + const host = definition.host ?? '127.0.0.1'; + if (!isLoopbackHostname(host === '::1' ? '[::1]' : host)) throw new Error('The standalone WebSocket broker must bind to a loopback host'); + return definition; +} + +function validateBridgeConfiguration(definition: Pick, 'agentPath' | 'clientPath' | 'timing'>): void { + const agentPath = definition.agentPath ?? defaultAgentWebSocketPath; + const clientPath = definition.clientPath ?? defaultClientWebSocketPath; + validatePath(agentPath); + validatePath(clientPath); + if (agentPath === clientPath) throw new Error('Agent and client WebSocket paths must be distinct'); + const timing = { ...defaultWebSocketBridgeTimingPolicy, ...definition.timing }; + validateTimeoutMilliseconds(timing.handshakeTimeoutMilliseconds, 'handshakeTimeoutMilliseconds'); + validateTimeoutMilliseconds(timing.pairingTimeoutMilliseconds, 'pairingTimeoutMilliseconds'); +} + +/** Configuration accepted by createStandaloneAuthenticatedWebSocketBridge and defineStandaloneWebSocket. */ +export type StandaloneWebSocketOptions = Omit, 'server'> & { + readonly host?: string; + readonly port?: number; +}; diff --git a/patches/@devframes__hub-ui@0.9.10.patch b/patches/@devframes__hub-ui@0.9.10.patch deleted file mode 100644 index e876d5f..0000000 --- a/patches/@devframes__hub-ui@0.9.10.patch +++ /dev/null @@ -1,32 +0,0 @@ -diff --git a/dist/client/embedded.js b/dist/client/embedded.js -index 36d996e28ee08a16b58d3e9f768a626a3dc1f563..4f64573e650d4b653ee1a5c8cea8454351f695c7 100644 ---- a/dist/client/embedded.js -+++ b/dist/client/embedded.js -@@ -11431,8 +11431,15 @@ function aS(e) { - }), n = /* @__PURE__ */ new Map(), r = !0, i = null; - async function a() { - let a = await e.rpc.call("devframes:plugin:messages:list", i), o = 0; -- a.full && n.clear(); -- for (let e of a.removedIds) n.delete(e); -+ let removedIds = a.removedIds; -+ if (a.full) { -+ const retainedIds = new Set(a.entries.map(entry => entry.id)); -+ removedIds = [...n.keys()].filter(id => !retainedIds.has(id)); -+ } -+ for (const id of removedIds) { -+ n.delete(id); -+ eS(id); -+ } - for (let e of a.entries) { - let t = n.get(e.id); - t || o++, iS(e, t, r) && $x(e), n.set(e.id, e); -diff --git a/dist/client/standalone/assets/DockStandalone-3_CzgPVL.js b/dist/client/standalone/assets/DockStandalone-3_CzgPVL.js -index 76be7462d86e8015ad312dfb33350a99b38e9efd..df9a767468ddfdba10dcb3450dbbf4053ba0e2b7 100644 ---- a/dist/client/standalone/assets/DockStandalone-3_CzgPVL.js -+++ b/dist/client/standalone/assets/DockStandalone-3_CzgPVL.js -@@ -1,3 +1,3 @@ - import{$ as e,A as t,B as n,C as r,E as i,F as a,G as o,I as s,K as c,L as l,M as u,O as d,P as f,Q as p,R as m,S as h,T as g,U as _,V as v,Z as y,_ as b,b as x,c as S,d as C,f as w,g as T,h as E,j as ee,k as te,l as D,m as ne,nt as re,o as ie,p as ae,q as oe,r as se,rt as ce,s as le,t as ue,u as O,v as de,w as fe,x as pe,z as me}from"./popup-B_knQQry.js";import{$ as he,A as ge,B as _e,C as ve,D as ye,E as k,Et as be,F as A,G as xe,H as Se,I as j,J as Ce,K as we,L as M,M as N,N as P,O as Te,Ot as Ee,P as F,Q as De,R as I,S as Oe,T as ke,U as Ae,V as je,W as Me,X as L,Y as Ne,Z as Pe,_ as Fe,_t as R,at as z,b as Ie,ct as Le,d as Re,dt as B,et as ze,f as Be,ft as Ve,g as He,gt as Ue,h as We,ht as V,it as Ge,j as Ke,k as H,l as qe,lt as Je,m as Ye,mt as U,nt as Xe,ot as Ze,p as Qe,pt as W,q as G,rt as $e,st as et,tt as K,u as tt,ut as nt,v as rt,vt as q,w as it,x as at,xt as ot,y as st,z as J}from"./index-BGXSUn0A.js";var ct=`*,:before,:after{box-sizing:border-box;border-style:solid;border-width:0;border-color:var(--un-hub-default-border-color,#e5e7eb)}:before,:after{--un-hub-content:""}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}body{line-height:inherit;margin:0}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-feature-settings:normal;font-variation-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-feature-settings:inherit;font-variation-settings:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:#0000;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{margin:0;padding:0;list-style:none}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--un-hub-text-opacity:100%}:host{--app-scrollbar-size:6px;--app-scrollbar-radius:1px;--app-scrollbar-thumb:#8884;--app-scrollbar-thumb-hover:#8885}::-webkit-scrollbar{width:var(--app-scrollbar-size)}::-webkit-scrollbar:horizontal{height:var(--app-scrollbar-size)}::-webkit-scrollbar-corner{background:0 0}::-webkit-scrollbar-thumb{background-color:var(--app-scrollbar-thumb);border-radius:var(--app-scrollbar-radius);transition:background .2s}::-webkit-scrollbar-thumb:hover{background-color:var(--app-scrollbar-thumb-hover)}::-webkit-scrollbar-track{border-radius:var(--app-scrollbar-radius);background:0 0}#devframes-anchor{z-index:2147483644;box-sizing:border-box;transform-origin:50%;width:0;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-size:15px;position:fixed;transform:translate(-50%,-50%)rotate(0)}#devframes-anchor #devframes-dock-container{width:max-content;height:var(--devframes-dock-height,40px);min-width:var(--devframes-dock-min-width,100px);display:flex;position:absolute;top:0;left:0;transform:translate(-50%,-50%)}#devframes-anchor.devframes-vertical #devframes-dock-container{transition-property:all;transition-duration:.5s;transition-timing-function:cubic-bezier(.4,0,.2,1);transform:translate(-50%,-50%)rotate(90deg)}#devframes-anchor #devframes-dock{touch-action:none;-webkit-user-select:none;user-select:none;--un-hub-border-opacity:.13;border-width:1px;border-color:rgb(153 153 153/var(--un-hub-border-opacity));--un-hub-backdrop-blur:blur(7px);height:100%;backdrop-filter:var(--un-hub-backdrop-blur) var(--un-hub-backdrop-brightness) var(--un-hub-backdrop-contrast) var(--un-hub-backdrop-grayscale) var(--un-hub-backdrop-hue-rotate) var(--un-hub-backdrop-invert) var(--un-hub-backdrop-opacity) var(--un-hub-backdrop-saturate) var(--un-hub-backdrop-sepia);--un-hub-text-opacity:1;color:rgb(51 51 51/var(--un-hub-text-opacity));--un-hub-shadow:var(--un-hub-shadow-inset) 0 1px 3px 0 var(--un-hub-shadow-color,#0000001a),var(--un-hub-shadow-inset) 0 1px 2px -1px var(--un-hub-shadow-color,#0000001a);box-shadow:var(--un-hub-ring-offset-shadow), var(--un-hub-ring-shadow), var(--un-hub-shadow);height:var(--devframes-dock-height,40px);width:calc-size(max-content, size);background-color:#fff;border-radius:.5rem;margin:auto;transition-property:all;transition-duration:.5s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:flex}.dark #devframes-anchor #devframes-dock{--un-hub-text-opacity:1;color:rgb(255 255 255/var(--un-hub-text-opacity));background-color:#111c}#devframes-anchor.devframes-minimized #devframes-dock{width:var(--devframes-dock-minimized-size,22px);height:var(--devframes-dock-minimized-size,22px);border-radius:9999px;padding:2px 0}#devframes-anchor:hover #devframes-glowing{opacity:.6}#devframes-anchor #devframes-glowing{pointer-events:none;opacity:0;width:var(--devframes-dock-glow-size,160px);height:var(--devframes-dock-glow-size,160px);filter:blur(var(--devframes-dock-glow-blur,60px));background-image:linear-gradient(45deg, var(--colors-primary-300,#adc77f), var(--colors-primary-500,#517158), var(--colors-primary-DEFAULT,#486954));border-radius:9999px;transition-property:all;transition-duration:1s;transition-timing-function:cubic-bezier(0,0,.2,1);position:absolute;top:0;left:0;transform:translate(-50%,-50%)}#devframes-edge-panel{transition-property:all;transition-duration:.5s;transition-timing-function:cubic-bezier(.16,1,.3,1)}@media print{#devframes-anchor{display:none}}.devframes-resize-handle-horizontal{cursor:ns-resize;border-radius:.375rem;height:10px;margin-top:-5px;margin-bottom:-5px;position:absolute;left:6px;right:6px}.devframes-resize-handle-vertical{cursor:ew-resize;border-radius:.375rem;width:10px;margin-left:-5px;margin-right:-5px;position:absolute;top:6px;bottom:0}.devframes-resize-handle-corner{border-radius:.375rem;width:14px;height:14px;margin:-6px;position:absolute}.devframes-resize-handle{z-index:30}.devframes-resize-handle:hover{background-color:#9ca3af1a}@keyframes devframes-shake{0%,to{transform:translate(0)}20%,60%{transform:translate(-5px)}40%,80%{transform:translate(5px)}}.devframes-shake{animation:.4s cubic-bezier(.36,.07,.19,.97) devframes-shake}@media (prefers-reduced-motion:reduce){.devframes-shake{animation:none}#devframes-edge-panel{transition:none!important}}*,:before,:after,::backdrop{--un-hub-rotate:0;--un-hub-rotate-x:0;--un-hub-rotate-y:0;--un-hub-rotate-z:0;--un-hub-scale-x:1;--un-hub-scale-y:1;--un-hub-scale-z:1;--un-hub-skew-x:0;--un-hub-skew-y:0;--un-hub-translate-x:0;--un-hub-translate-y:0;--un-hub-translate-z:0;--un-hub-pan-x: ;--un-hub-pan-y: ;--un-hub-pinch-zoom: ;--un-hub-scroll-snap-strictness:proximity;--un-hub-ordinal: ;--un-hub-slashed-zero: ;--un-hub-numeric-figure: ;--un-hub-numeric-spacing: ;--un-hub-numeric-fraction: ;--un-hub-border-spacing-x:0;--un-hub-border-spacing-y:0;--un-hub-ring-offset-shadow:0 0 #0000;--un-hub-ring-shadow:0 0 #0000;--un-hub-shadow-inset: ;--un-hub-shadow:0 0 #0000;--un-hub-ring-inset: ;--un-hub-ring-offset-width:0px;--un-hub-ring-offset-color:#fff;--un-hub-ring-width:0px;--un-hub-ring-color:#93c5fd80;--un-hub-blur: ;--un-hub-brightness: ;--un-hub-contrast: ;--un-hub-drop-shadow: ;--un-hub-grayscale: ;--un-hub-hue-rotate: ;--un-hub-invert: ;--un-hub-saturate: ;--un-hub-sepia: ;--un-hub-backdrop-blur: ;--un-hub-backdrop-brightness: ;--un-hub-backdrop-contrast: ;--un-hub-backdrop-grayscale: ;--un-hub-backdrop-hue-rotate: ;--un-hub-backdrop-invert: ;--un-hub-backdrop-opacity: ;--un-hub-backdrop-saturate: ;--un-hub-backdrop-sepia: }.i-catppuccin\\:folder{background:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 16 16' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='none' stroke='%23cad3f5' stroke-linecap='round' stroke-linejoin='round' d='M4.5 4.5H12c.83 0 1.5.67 1.5 1.5v6c0 .83-.67 1.5-1.5 1.5H2A1.5 1.5 0 0 1 .5 12V3.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v1'/%3E%3C/svg%3E") 0 0/100% 100% no-repeat;width:1.11em;height:1.11em}.i-ph-arrow-clockwise{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M240 56v48a8 8 0 0 1-8 8h-48a8 8 0 0 1 0-16h27.4l-26.59-24.36l-.25-.24a80 80 0 1 0-1.67 114.78a8 8 0 0 1 11 11.63A95.44 95.44 0 0 1 128 224h-1.32a96 96 0 1 1 69.07-164L224 85.8V56a8 8 0 1 1 16 0'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-arrow-clockwise-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M216 128a88 88 0 1 1-88-88a88 88 0 0 1 88 88' opacity='.2'/%3E%3Cpath d='M240 56v48a8 8 0 0 1-8 8h-48a8 8 0 0 1 0-16h27.4l-26.59-24.36l-.25-.24a80 80 0 1 0-1.67 114.78a8 8 0 0 1 11 11.63A95.44 95.44 0 0 1 128 224h-1.32a96 96 0 1 1 69.07-164L224 85.8V56a8 8 0 1 1 16 0'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-arrow-counter-clockwise{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M224 128a96 96 0 0 1-94.71 96H128a95.38 95.38 0 0 1-65.9-26.2a8 8 0 0 1 11-11.63a80 80 0 1 0-1.67-114.78a3 3 0 0 1-.26.25L44.59 96H72a8 8 0 0 1 0 16H24a8 8 0 0 1-8-8V56a8 8 0 0 1 16 0v29.8L60.25 60A96 96 0 0 1 224 128'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-arrow-square-out-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M184 80v128a8 8 0 0 1-8 8H48a8 8 0 0 1-8-8V80a8 8 0 0 1 8-8h128a8 8 0 0 1 8 8' opacity='.2'/%3E%3Cpath d='M224 104a8 8 0 0 1-16 0V59.32l-66.33 66.34a8 8 0 0 1-11.32-11.32L196.68 48H152a8 8 0 0 1 0-16h64a8 8 0 0 1 8 8Zm-40 24a8 8 0 0 0-8 8v72H48V80h72a8 8 0 0 0 0-16H48a16 16 0 0 0-16 16v128a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-72a8 8 0 0 0-8-8'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-arrows-counter-clockwise-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M216 128a88 88 0 1 1-88-88a88 88 0 0 1 88 88' opacity='.2'/%3E%3Cpath d='M88 104H40a8 8 0 0 1-8-8V48a8 8 0 0 1 16 0v28.69l14.63-14.63A95.43 95.43 0 0 1 130 33.94h.53a95.36 95.36 0 0 1 67.07 27.33a8 8 0 0 1-11.18 11.44a79.52 79.52 0 0 0-55.89-22.77h-.45a79.56 79.56 0 0 0-56.14 23.43L59.31 88H88a8 8 0 0 1 0 16m128 48h-48a8 8 0 0 0 0 16h28.69l-14.63 14.63a79.56 79.56 0 0 1-56.13 23.43h-.45a79.52 79.52 0 0 1-55.89-22.77a8 8 0 1 0-11.18 11.44a95.36 95.36 0 0 0 67.07 27.33h.52a95.43 95.43 0 0 0 67.36-28.12L208 179.31V208a8 8 0 0 0 16 0v-48a8 8 0 0 0-8-8'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-arrows-out-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M208 48v160H48V48Z' opacity='.2'/%3E%3Cpath d='M216 48v48a8 8 0 0 1-16 0V67.31l-42.34 42.35a8 8 0 0 1-11.32-11.32L188.69 56H160a8 8 0 0 1 0-16h48a8 8 0 0 1 8 8M98.34 146.34L56 188.69V160a8 8 0 0 0-16 0v48a8 8 0 0 0 8 8h48a8 8 0 0 0 0-16H67.31l42.35-42.34a8 8 0 0 0-11.32-11.32M208 152a8 8 0 0 0-8 8v28.69l-42.34-42.35a8 8 0 0 0-11.32 11.32L188.69 200H160a8 8 0 0 0 0 16h48a8 8 0 0 0 8-8v-48a8 8 0 0 0-8-8M67.31 56H96a8 8 0 0 0 0-16H48a8 8 0 0 0-8 8v48a8 8 0 0 0 16 0V67.31l42.34 42.35a8 8 0 0 0 11.32-11.32Z'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-cards-three-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M216 104v96a8 8 0 0 1-8 8H48a8 8 0 0 1-8-8v-96a8 8 0 0 1 8-8h160a8 8 0 0 1 8 8' opacity='.2'/%3E%3Cpath d='M208 88H48a16 16 0 0 0-16 16v96a16 16 0 0 0 16 16h160a16 16 0 0 0 16-16v-96a16 16 0 0 0-16-16m0 112H48v-96h160zM48 64a8 8 0 0 1 8-8h144a8 8 0 0 1 0 16H56a8 8 0 0 1-8-8m16-32a8 8 0 0 1 8-8h112a8 8 0 0 1 0 16H72a8 8 0 0 1-8-8'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-caret-down,.i-ph\\:caret-down{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='m213.66 101.66l-80 80a8 8 0 0 1-11.32 0l-80-80a8 8 0 0 1 11.32-11.32L128 164.69l74.34-74.35a8 8 0 0 1 11.32 11.32'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-caret-left,.i-ph\\:caret-left{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M165.66 202.34a8 8 0 0 1-11.32 11.32l-80-80a8 8 0 0 1 0-11.32l80-80a8 8 0 0 1 11.32 11.32L91.31 128Z'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-caret-right,.i-ph\\:caret-right{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='m181.66 133.66l-80 80a8 8 0 0 1-11.32-11.32L164.69 128L90.34 53.66a8 8 0 0 1 11.32-11.32l80 80a8 8 0 0 1 0 11.32'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-caret-up{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M213.66 165.66a8 8 0 0 1-11.32 0L128 91.31l-74.34 74.35a8 8 0 0 1-11.32-11.32l80-80a8 8 0 0 1 11.32 0l80 80a8 8 0 0 1 0 11.32'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-check-bold,.i-ph\\:check-bold{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='m232.49 80.49l-128 128a12 12 0 0 1-17 0l-56-56a12 12 0 1 1 17-17L96 183L215.51 63.51a12 12 0 0 1 17 17Z'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-circle-notch{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M232 128a104 104 0 0 1-208 0c0-41 23.81-78.36 60.66-95.27a8 8 0 0 1 6.68 14.54C60.15 61.59 40 93.27 40 128a88 88 0 0 0 176 0c0-34.73-20.15-66.41-51.34-80.73a8 8 0 0 1 6.68-14.54C208.19 49.64 232 87 232 128'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-dots-six-vertical{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M104 60a12 12 0 1 1-12-12a12 12 0 0 1 12 12m60 12a12 12 0 1 0-12-12a12 12 0 0 0 12 12m-72 44a12 12 0 1 0 12 12a12 12 0 0 0-12-12m72 0a12 12 0 1 0 12 12a12 12 0 0 0-12-12m-72 68a12 12 0 1 0 12 12a12 12 0 0 0-12-12m72 0a12 12 0 1 0 12 12a12 12 0 0 0-12-12'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-dots-three-outline-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M152 128a24 24 0 1 1-24-24a24 24 0 0 1 24 24M48 104a24 24 0 1 0 24 24a24 24 0 0 0-24-24m160 0a24 24 0 1 0 24 24a24 24 0 0 0-24-24' opacity='.2'/%3E%3Cpath d='M128 96a32 32 0 1 0 32 32a32 32 0 0 0-32-32m0 48a16 16 0 1 1 16-16a16 16 0 0 1-16 16M48 96a32 32 0 1 0 32 32a32 32 0 0 0-32-32m0 48a16 16 0 1 1 16-16a16 16 0 0 1-16 16m160-48a32 32 0 1 0 32 32a32 32 0 0 0-32-32m0 48a16 16 0 1 1 16-16a16 16 0 0 1-16 16'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-eye-slash{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M53.92 34.62a8 8 0 1 0-11.84 10.76l19.24 21.17C25 88.84 9.38 123.2 8.69 124.76a8 8 0 0 0 0 6.5c.35.79 8.82 19.57 27.65 38.4C61.43 194.74 93.12 208 128 208a127.1 127.1 0 0 0 52.07-10.83l22 24.21a8 8 0 1 0 11.84-10.76Zm47.33 75.84l41.67 45.85a32 32 0 0 1-41.67-45.85M128 192c-30.78 0-57.67-11.19-79.93-33.25A133.2 133.2 0 0 1 25 128c4.69-8.79 19.66-33.39 47.35-49.38l18 19.75a48 48 0 0 0 63.66 70l14.73 16.2A112 112 0 0 1 128 192m6-95.43a8 8 0 0 1 3-15.72a48.16 48.16 0 0 1 38.77 42.64a8 8 0 0 1-7.22 8.71a6 6 0 0 1-.75 0a8 8 0 0 1-8-7.26A32.09 32.09 0 0 0 134 96.57m113.28 34.69c-.42.94-10.55 23.37-33.36 43.8a8 8 0 1 1-10.67-11.92a132.8 132.8 0 0 0 27.8-35.14a133.2 133.2 0 0 0-23.12-30.77C185.67 75.19 158.78 64 128 64a118.4 118.4 0 0 0-19.36 1.57A8 8 0 1 1 106 49.79A134 134 0 0 1 128 48c34.88 0 66.57 13.26 91.66 38.35c18.83 18.83 27.3 37.62 27.65 38.41a8 8 0 0 1 0 6.5Z'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-eye-slash-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M128 56c-80 0-112 72-112 72s32 72 112 72s112-72 112-72s-32-72-112-72m0 112a40 40 0 1 1 40-40a40 40 0 0 1-40 40' opacity='.2'/%3E%3Cpath d='M53.92 34.62a8 8 0 1 0-11.84 10.76l19.24 21.17C25 88.84 9.38 123.2 8.69 124.76a8 8 0 0 0 0 6.5c.35.79 8.82 19.57 27.65 38.4C61.43 194.74 93.12 208 128 208a127.1 127.1 0 0 0 52.07-10.83l22 24.21a8 8 0 1 0 11.84-10.76Zm47.33 75.84l41.67 45.85a32 32 0 0 1-41.67-45.85M128 192c-30.78 0-57.67-11.19-79.93-33.25A133.2 133.2 0 0 1 25 128c4.69-8.79 19.66-33.39 47.35-49.38l18 19.75a48 48 0 0 0 63.66 70l14.73 16.2A112 112 0 0 1 128 192m6-95.43a8 8 0 0 1 3-15.72a48.16 48.16 0 0 1 38.77 42.64a8 8 0 0 1-7.22 8.71a6 6 0 0 1-.75 0a8 8 0 0 1-8-7.26A32.09 32.09 0 0 0 134 96.57m113.28 34.69c-.42.94-10.55 23.37-33.36 43.8a8 8 0 1 1-10.67-11.92a132.8 132.8 0 0 0 27.8-35.14a133.2 133.2 0 0 0-23.12-30.77C185.67 75.19 158.78 64 128 64a118.4 118.4 0 0 0-19.36 1.57A8 8 0 1 1 106 49.79A134 134 0 0 1 128 48c34.88 0 66.57 13.26 91.66 38.35c18.83 18.83 27.3 37.62 27.65 38.41a8 8 0 0 1 0 6.5Z'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-globe{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M128 24a104 104 0 1 0 104 104A104.12 104.12 0 0 0 128 24m88 104a87.6 87.6 0 0 1-3.33 24h-38.51a157.4 157.4 0 0 0 0-48h38.51a87.6 87.6 0 0 1 3.33 24m-114 40h52a115.1 115.1 0 0 1-26 45a115.3 115.3 0 0 1-26-45m-3.9-16a140.8 140.8 0 0 1 0-48h59.88a140.8 140.8 0 0 1 0 48ZM40 128a87.6 87.6 0 0 1 3.33-24h38.51a157.4 157.4 0 0 0 0 48H43.33A87.6 87.6 0 0 1 40 128m114-40h-52a115.1 115.1 0 0 1 26-45a115.3 115.3 0 0 1 26 45m52.33 0h-35.62a135.3 135.3 0 0 0-22.3-45.6A88.29 88.29 0 0 1 206.37 88Zm-98.74-45.6A135.3 135.3 0 0 0 85.29 88H49.63a88.29 88.29 0 0 1 57.96-45.6M49.63 168h35.66a135.3 135.3 0 0 0 22.3 45.6A88.29 88.29 0 0 1 49.63 168m98.78 45.6a135.3 135.3 0 0 0 22.3-45.6h35.66a88.29 88.29 0 0 1-57.96 45.6'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-keyboard-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M232 64v128a8 8 0 0 1-8 8H32a8 8 0 0 1-8-8V64a8 8 0 0 1 8-8h192a8 8 0 0 1 8 8' opacity='.2'/%3E%3Cpath d='M224 48H32a16 16 0 0 0-16 16v128a16 16 0 0 0 16 16h192a16 16 0 0 0 16-16V64a16 16 0 0 0-16-16m0 144H32V64h192zm-16-64a8 8 0 0 1-8 8H56a8 8 0 0 1 0-16h144a8 8 0 0 1 8 8m0-32a8 8 0 0 1-8 8H56a8 8 0 0 1 0-16h144a8 8 0 0 1 8 8M72 160a8 8 0 0 1-8 8h-8a8 8 0 0 1 0-16h8a8 8 0 0 1 8 8m96 0a8 8 0 0 1-8 8H96a8 8 0 0 1 0-16h64a8 8 0 0 1 8 8m40 0a8 8 0 0 1-8 8h-8a8 8 0 0 1 0-16h8a8 8 0 0 1 8 8'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-laptop-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M216 72v104H40V72a16 16 0 0 1 16-16h144a16 16 0 0 1 16 16' opacity='.2'/%3E%3Cpath d='M232 168h-8V72a24 24 0 0 0-24-24H56a24 24 0 0 0-24 24v96h-8a8 8 0 0 0-8 8v16a24 24 0 0 0 24 24h176a24 24 0 0 0 24-24v-16a8 8 0 0 0-8-8M48 72a8 8 0 0 1 8-8h144a8 8 0 0 1 8 8v96H48Zm176 120a8 8 0 0 1-8 8H40a8 8 0 0 1-8-8v-8h192ZM152 88a8 8 0 0 1-8 8h-32a8 8 0 0 1 0-16h32a8 8 0 0 1 8 8'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-layout-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M104 104v104H40a8 8 0 0 1-8-8v-96Z' opacity='.2'/%3E%3Cpath d='M216 40H40a16 16 0 0 0-16 16v144a16 16 0 0 0 16 16h176a16 16 0 0 0 16-16V56a16 16 0 0 0-16-16m0 16v40H40V56ZM40 112h56v88H40Zm176 88H112v-88h104z'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-magnifying-glass-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M192 112a80 80 0 1 1-80-80a80 80 0 0 1 80 80' opacity='.2'/%3E%3Cpath d='m229.66 218.34l-50.06-50.06a88.21 88.21 0 1 0-11.32 11.31l50.06 50.07a8 8 0 0 0 11.32-11.32M40 112a72 72 0 1 1 72 72a72.08 72.08 0 0 1-72-72'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-moon-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M227.89 147.89A96 96 0 1 1 108.11 28.11a96.09 96.09 0 0 0 119.78 119.78' opacity='.2'/%3E%3Cpath d='M233.54 142.23a8 8 0 0 0-8-2a88.08 88.08 0 0 1-109.8-109.8a8 8 0 0 0-10-10a104.84 104.84 0 0 0-52.91 37A104 104 0 0 0 136 224a103.1 103.1 0 0 0 62.52-20.88a104.84 104.84 0 0 0 37-52.91a8 8 0 0 0-1.98-7.98m-44.64 48.11A88 88 0 0 1 65.66 67.11a89 89 0 0 1 31.4-26A106 106 0 0 0 96 56a104.11 104.11 0 0 0 104 104a106 106 0 0 0 14.92-1.06a89 89 0 0 1-26.02 31.4'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-paint-brush-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M224 32c0 32.81-31.64 67.43-58.64 91.05A84.4 84.4 0 0 0 133 90.64c23.57-27 58.19-58.64 91-58.64' opacity='.2'/%3E%3Cpath d='M232 32a8 8 0 0 0-8-8c-44.08 0-89.31 49.71-114.43 82.63A60 60 0 0 0 32 164c0 30.88-19.54 44.73-20.47 45.37A8 8 0 0 0 16 224h76a60 60 0 0 0 57.37-77.57C182.3 121.31 232 76.08 232 32M92 208H34.63C41.38 198.41 48 183.92 48 164a44 44 0 1 1 44 44m32.42-94.45q5.14-6.66 10.09-12.55A76.2 76.2 0 0 1 155 121.49q-5.9 4.94-12.55 10.09a60.5 60.5 0 0 0-18.03-18.03m42.7-2.68a92.6 92.6 0 0 0-22-22c31.78-34.53 55.75-45 69.9-47.91c-2.85 14.16-13.37 38.13-47.9 69.91'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-push-pin{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='m235.32 81.37l-60.69-60.68a16 16 0 0 0-22.63 0l-53.63 53.8c-10.66-3.34-35-7.37-60.4 13.14a16 16 0 0 0-1.29 23.78L85 159.71l-42.66 42.63a8 8 0 0 0 11.32 11.32L96.29 171l48.29 48.29A16 16 0 0 0 155.9 224h1.13a15.93 15.93 0 0 0 11.64-6.33c19.64-26.1 17.75-47.32 13.19-60L235.33 104a16 16 0 0 0-.01-22.63M224 92.69l-57.27 57.46a8 8 0 0 0-1.49 9.22c9.46 18.93-1.8 38.59-9.34 48.62L48 100.08c12.08-9.74 23.64-12.31 32.48-12.31A40.1 40.1 0 0 1 96.81 91a8 8 0 0 0 9.25-1.51L163.32 32L224 92.68Z'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-push-pin-fill{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='m235.33 104l-53.47 53.65c4.56 12.67 6.45 33.89-13.19 60A15.93 15.93 0 0 1 157 224h-1.13a16 16 0 0 1-11.32-4.69L96.29 171l-42.63 42.66a8 8 0 0 1-11.32-11.32L85 159.71l-48.3-48.3A16 16 0 0 1 38 87.63c25.42-20.51 49.75-16.48 60.4-13.14L152 20.7a16 16 0 0 1 22.63 0l60.69 60.68a16 16 0 0 1 .01 22.62'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-rocket-launch-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M184 120v61.65a8 8 0 0 1-2.34 5.65l-34.35 34.35a8 8 0 0 1-13.57-4.53L128 176Zm-48-48H74.35a8 8 0 0 0-5.65 2.34l-34.35 34.35a8 8 0 0 0 4.53 13.57L80 128ZM40 216c37.65 0 50.69-19.69 54.56-28.18l-26.38-26.38C59.69 165.31 40 178.35 40 216' opacity='.2'/%3E%3Cpath d='M223.85 47.12a16 16 0 0 0-15-15c-12.58-.75-44.73.4-71.41 27.07L132.69 64H74.36A15.9 15.9 0 0 0 63 68.68L28.7 103a16 16 0 0 0 9.07 27.16l38.47 5.37l44.21 44.21l5.37 38.49a15.94 15.94 0 0 0 10.78 12.92a16.1 16.1 0 0 0 5.1.83a15.9 15.9 0 0 0 11.3-4.68l34.32-34.3a15.9 15.9 0 0 0 4.68-11.36v-58.33l4.77-4.77c26.68-26.68 27.83-58.83 27.08-71.42M74.36 80h42.33l-39.53 39.52L40 114.34Zm74.41-9.45a76.65 76.65 0 0 1 59.11-22.47a76.46 76.46 0 0 1-22.42 59.16L128 164.68L91.32 128ZM176 181.64L141.67 216l-5.19-37.17L176 139.31Zm-74.16 9.5C97.34 201 82.29 224 40 224a8 8 0 0 1-8-8c0-42.29 23-57.34 32.86-61.85a8 8 0 0 1 6.64 14.56c-6.43 2.93-20.62 12.36-23.12 38.91c26.55-2.5 36-16.69 38.91-23.12a8 8 0 1 1 14.56 6.64Z'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-shield-check-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M216 56v56c0 96-88 120-88 120s-88-24-88-120V56a8 8 0 0 1 8-8h160a8 8 0 0 1 8 8' opacity='.2'/%3E%3Cpath d='M208 40H48a16 16 0 0 0-16 16v56c0 52.72 25.52 84.67 46.93 102.19c23.06 18.86 46 25.26 47 25.53a8 8 0 0 0 4.2 0c1-.27 23.91-6.67 47-25.53C198.48 196.67 224 164.72 224 112V56a16 16 0 0 0-16-16m0 72c0 37.07-13.66 67.16-40.6 89.42a129.3 129.3 0 0 1-39.4 22.2a128.3 128.3 0 0 1-38.92-21.81C61.82 179.51 48 149.3 48 112V56h160ZM82.34 141.66a8 8 0 0 1 11.32-11.32L112 148.69l50.34-50.35a8 8 0 0 1 11.32 11.32l-56 56a8 8 0 0 1-11.32 0Z'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-sign-out-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M224 56v144a16 16 0 0 1-16 16H48V40h160a16 16 0 0 1 16 16' opacity='.2'/%3E%3Cpath d='M120 216a8 8 0 0 1-8 8H48a8 8 0 0 1-8-8V40a8 8 0 0 1 8-8h64a8 8 0 0 1 0 16H56v160h56a8 8 0 0 1 8 8m109.66-93.66l-40-40a8 8 0 0 0-11.32 11.32L204.69 120H112a8 8 0 0 0 0 16h92.69l-26.35 26.34a8 8 0 0 0 11.32 11.32l40-40a8 8 0 0 0 0-11.32'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-spinner-gap-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96' opacity='.2'/%3E%3Cpath d='M136 32v32a8 8 0 0 1-16 0V32a8 8 0 0 1 16 0m88 88h-32a8 8 0 0 0 0 16h32a8 8 0 0 0 0-16m-45.09 47.6a8 8 0 0 0-11.31 11.31l22.62 22.63a8 8 0 0 0 11.32-11.32ZM128 184a8 8 0 0 0-8 8v32a8 8 0 0 0 16 0v-32a8 8 0 0 0-8-8m-50.91-16.4l-22.63 22.62a8 8 0 0 0 11.32 11.32l22.62-22.63a8 8 0 0 0-11.31-11.31M72 128a8 8 0 0 0-8-8H32a8 8 0 0 0 0 16h32a8 8 0 0 0 8-8m-6.22-73.54a8 8 0 0 0-11.32 11.32L77.09 88.4A8 8 0 0 0 88.4 77.09Z'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-square-half-bottom-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M208 128v72a8 8 0 0 1-8 8H56a8 8 0 0 1-8-8v-72Z' opacity='.2'/%3E%3Cpath d='M200 40H56a16 16 0 0 0-16 16v144a16 16 0 0 0 16 16h144a16 16 0 0 0 16-16V56a16 16 0 0 0-16-16m0 16v64H56V56Zm0 144H56v-64h144z'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-sun-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M184 128a56 56 0 1 1-56-56a56 56 0 0 1 56 56' opacity='.2'/%3E%3Cpath d='M120 40V16a8 8 0 0 1 16 0v24a8 8 0 0 1-16 0m72 88a64 64 0 1 1-64-64a64.07 64.07 0 0 1 64 64m-16 0a48 48 0 1 0-48 48a48.05 48.05 0 0 0 48-48M58.34 69.66a8 8 0 0 0 11.32-11.32l-16-16a8 8 0 0 0-11.32 11.32Zm0 116.68l-16 16a8 8 0 0 0 11.32 11.32l16-16a8 8 0 0 0-11.32-11.32M192 72a8 8 0 0 0 5.66-2.34l16-16a8 8 0 0 0-11.32-11.32l-16 16A8 8 0 0 0 192 72m5.66 114.34a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32-11.32ZM48 128a8 8 0 0 0-8-8H16a8 8 0 0 0 0 16h24a8 8 0 0 0 8-8m80 80a8 8 0 0 0-8 8v24a8 8 0 0 0 16 0v-24a8 8 0 0 0-8-8m112-88h-24a8 8 0 0 0 0 16h24a8 8 0 0 0 0-16'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-warning-duotone,.i-ph\\:warning-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M215.46 216H40.54c-12.62 0-20.54-13.21-14.41-23.91l87.46-151.87c6.3-11 22.52-11 28.82 0l87.46 151.87c6.13 10.7-1.79 23.91-14.41 23.91' opacity='.2'/%3E%3Cpath d='M236.8 188.09L149.35 36.22a24.76 24.76 0 0 0-42.7 0L19.2 188.09a23.51 23.51 0 0 0 0 23.72A24.35 24.35 0 0 0 40.55 224h174.9a24.35 24.35 0 0 0 21.33-12.19a23.51 23.51 0 0 0 .02-23.72m-13.87 15.71a8.5 8.5 0 0 1-7.48 4.2H40.55a8.5 8.5 0 0 1-7.48-4.2a7.59 7.59 0 0 1 0-7.72l87.45-151.87a8.75 8.75 0 0 1 15 0l87.45 151.87a7.59 7.59 0 0 1-.04 7.72M120 144v-40a8 8 0 0 1 16 0v40a8 8 0 0 1-16 0m20 36a12 12 0 1 1-12-12a12 12 0 0 1 12 12'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-warning-fill{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M236.8 188.09L149.35 36.22a24.76 24.76 0 0 0-42.7 0L19.2 188.09a23.51 23.51 0 0 0 0 23.72A24.35 24.35 0 0 0 40.55 224h174.9a24.35 24.35 0 0 0 21.33-12.19a23.51 23.51 0 0 0 .02-23.72M120 104a8 8 0 0 1 16 0v40a8 8 0 0 1-16 0Zm8 88a12 12 0 1 1 12-12a12 12 0 0 1-12 12'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-wrench-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M224 96a64 64 0 0 1-94.94 56L73 217a24 24 0 0 1-34-34l65-56.06a64 64 0 0 1 80-90.29L144 80l5.66 26.34L176 112l43.35-40A63.8 63.8 0 0 1 224 96' opacity='.2'/%3E%3Cpath d='M226.76 69a8 8 0 0 0-12.84-2.88l-40.3 37.19l-17.23-3.7l-3.7-17.23l37.19-40.3A8 8 0 0 0 187 29.24A72 72 0 0 0 88 96a72.3 72.3 0 0 0 6 28.94L33.79 177c-.15.12-.29.26-.43.39a32 32 0 0 0 45.26 45.26c.13-.13.27-.28.39-.42L131.06 162A72 72 0 0 0 232 96a71.6 71.6 0 0 0-5.24-27M160 152a56.14 56.14 0 0 1-27.07-7a8 8 0 0 0-9.92 1.77l-55.9 64.74a16 16 0 0 1-22.62-22.62L109.18 133a8 8 0 0 0 1.77-9.93a56 56 0 0 1 58.36-82.31l-31.2 33.81a8 8 0 0 0-1.94 7.1l5.66 26.33a8 8 0 0 0 6.14 6.14l26.35 5.66a8 8 0 0 0 7.1-1.94l33.81-31.2A56.06 56.06 0 0 1 160 152'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph-x,.i-ph\\:x{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M205.66 194.34a8 8 0 0 1-11.32 11.32L128 139.31l-66.34 66.35a8 8 0 0 1-11.32-11.32L116.69 128L50.34 61.66a8 8 0 0 1 11.32-11.32L128 116.69l66.34-66.35a8 8 0 0 1 11.32 11.32L139.31 128Z'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph\\:bug-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M208 128v16a80 80 0 0 1-160 0v-16Z' opacity='.2'/%3E%3Cpath d='M144 92a12 12 0 1 1 12 12a12 12 0 0 1-12-12m-44-12a12 12 0 1 0 12 12a12 12 0 0 0-12-12m116 64a87.8 87.8 0 0 1-3 23l22.24 9.72A8 8 0 0 1 232 192a7.9 7.9 0 0 1-3.2-.67L207.38 182a88 88 0 0 1-158.76 0l-21.42 9.33a7.9 7.9 0 0 1-3.2.67a8 8 0 0 1-3.2-15.33L43 167a87.8 87.8 0 0 1-3-23v-8H16a8 8 0 0 1 0-16h24v-8a87.8 87.8 0 0 1 3-23l-22.2-9.67a8 8 0 1 1 6.4-14.66L48.62 74a88 88 0 0 1 158.76 0l21.42-9.36a8 8 0 0 1 6.4 14.66L213 89.05a87.8 87.8 0 0 1 3 23v8h24a8 8 0 0 1 0 16h-24ZM56 120h144v-8a72 72 0 0 0-144 0Zm64 95.54V136H56v8a72.08 72.08 0 0 0 64 71.54M200 144v-8h-64v79.54A72.08 72.08 0 0 0 200 144'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph\\:caret-up-fill{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M215.39 163.06A8 8 0 0 1 208 168H48a8 8 0 0 1-5.66-13.66l80-80a8 8 0 0 1 11.32 0l80 80a8 8 0 0 1 1.73 8.72'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph\\:check{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='m229.66 77.66l-128 128a8 8 0 0 1-11.32 0l-56-56a8 8 0 0 1 11.32-11.32L96 188.69L218.34 66.34a8 8 0 0 1 11.32 11.32'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph\\:check-circle{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M173.66 98.34a8 8 0 0 1 0 11.32l-56 56a8 8 0 0 1-11.32 0l-24-24a8 8 0 0 1 11.32-11.32L112 148.69l50.34-50.35a8 8 0 0 1 11.32 0M232 128A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph\\:check-circle-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96' opacity='.2'/%3E%3Cpath d='M173.66 98.34a8 8 0 0 1 0 11.32l-56 56a8 8 0 0 1-11.32 0l-24-24a8 8 0 0 1 11.32-11.32L112 148.69l50.34-50.35a8 8 0 0 1 11.32 0M232 128A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph\\:circle-notch-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96' opacity='.2'/%3E%3Cpath d='M232 128a104 104 0 0 1-208 0c0-41 23.81-78.36 60.66-95.27a8 8 0 0 1 6.68 14.54C60.15 61.59 40 93.27 40 128a88 88 0 0 0 176 0c0-34.73-20.15-66.41-51.34-80.73a8 8 0 0 1 6.68-14.54C208.19 49.64 232 87 232 128'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph\\:cloud-warning-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M240 127.62a80 80 0 0 1-80 80H72A56 56 0 1 1 85.92 97.36v.1A80 80 0 0 1 240 127.62' opacity='.2'/%3E%3Cpath d='M160 40a88.09 88.09 0 0 0-78.71 48.67A64 64 0 1 0 72 216h88a88 88 0 0 0 0-176m0 160H72a48 48 0 0 1 0-96c1.1 0 2.2 0 3.29.11A88 88 0 0 0 72 128a8 8 0 0 0 16 0a72 72 0 1 1 72 72m-8-72V88a8 8 0 0 1 16 0v40a8 8 0 0 1-16 0m20 36a12 12 0 1 1-12-12a12 12 0 0 1 12 12'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph\\:copy{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M216 32H88a8 8 0 0 0-8 8v40H40a8 8 0 0 0-8 8v128a8 8 0 0 0 8 8h128a8 8 0 0 0 8-8v-40h40a8 8 0 0 0 8-8V40a8 8 0 0 0-8-8m-56 176H48V96h112Zm48-48h-32V88a8 8 0 0 0-8-8H96V48h112Z'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph\\:info-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96' opacity='.2'/%3E%3Cpath d='M144 176a8 8 0 0 1-8 8a16 16 0 0 1-16-16v-40a8 8 0 0 1 0-16a16 16 0 0 1 16 16v40a8 8 0 0 1 8 8m88-48A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88m-92-32a12 12 0 1 0-12-12a12 12 0 0 0 12 12'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph\\:magnifying-glass{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='m229.66 218.34l-50.07-50.06a88.11 88.11 0 1 0-11.31 11.31l50.06 50.07a8 8 0 0 0 11.32-11.32M40 112a72 72 0 1 1 72 72a72.08 72.08 0 0 1-72-72'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph\\:minus{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M224 128a8 8 0 0 1-8 8H40a8 8 0 0 1 0-16h176a8 8 0 0 1 8 8'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph\\:plus{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M224 128a8 8 0 0 1-8 8h-80v80a8 8 0 0 1-16 0v-80H40a8 8 0 0 1 0-16h80V40a8 8 0 0 1 16 0v80h80a8 8 0 0 1 8 8'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph\\:puzzle-piece-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M204 168a28 28 0 0 0 12-2.69V208a8 8 0 0 1-8 8H64a8 8 0 0 1-8-8v-42.69a28 28 0 1 1 0-50.62V72a8 8 0 0 1 8-8h46.69a28 28 0 1 1 50.61 0H208a8 8 0 0 1 8 8v42.69A28 28 0 1 0 204 168' opacity='.2'/%3E%3Cpath d='M220.27 158.54a8 8 0 0 0-7.7-.46a20 20 0 1 1 0-36.16a8 8 0 0 0 11.43-7.23V72a16 16 0 0 0-16-16h-36.22a35 35 0 0 0 .22-4a36.15 36.15 0 0 0-11.36-26.25a36 36 0 0 0-60.55 23.63a36.6 36.6 0 0 0 .14 6.62H64a16 16 0 0 0-16 16v32.22a35 35 0 0 0-4-.22a36.12 36.12 0 0 0-26.24 11.36a35.7 35.7 0 0 0-9.69 27a36.08 36.08 0 0 0 33.31 33.6a36.6 36.6 0 0 0 6.62-.14V208a16 16 0 0 0 16 16h144a16 16 0 0 0 16-16v-42.69a8 8 0 0 0-3.73-6.77M208 208H64v-42.69a8 8 0 0 0-11.43-7.23a20 20 0 1 1 0-36.16A8 8 0 0 0 64 114.69V72h46.69a8 8 0 0 0 7.23-11.43a20 20 0 1 1 36.16 0A8 8 0 0 0 161.31 72H208v32.23a35.7 35.7 0 0 0-6.62-.14A36 36 0 0 0 204 176a35 35 0 0 0 4-.22Z'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph\\:seal-check{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M225.86 102.82c-3.77-3.94-7.67-8-9.14-11.57c-1.36-3.27-1.44-8.69-1.52-13.94c-.15-9.76-.31-20.82-8-28.51s-18.75-7.85-28.51-8c-5.25-.08-10.67-.16-13.94-1.52c-3.56-1.47-7.63-5.37-11.57-9.14C146.28 23.51 138.44 16 128 16s-18.27 7.51-25.18 14.14c-3.94 3.77-8 7.67-11.57 9.14c-3.25 1.36-8.69 1.44-13.94 1.52c-9.76.15-20.82.31-28.51 8s-7.8 18.75-8 28.51c-.08 5.25-.16 10.67-1.52 13.94c-1.47 3.56-5.37 7.63-9.14 11.57C23.51 109.72 16 117.56 16 128s7.51 18.27 14.14 25.18c3.77 3.94 7.67 8 9.14 11.57c1.36 3.27 1.44 8.69 1.52 13.94c.15 9.76.31 20.82 8 28.51s18.75 7.85 28.51 8c5.25.08 10.67.16 13.94 1.52c3.56 1.47 7.63 5.37 11.57 9.14c6.9 6.63 14.74 14.14 25.18 14.14s18.27-7.51 25.18-14.14c3.94-3.77 8-7.67 11.57-9.14c3.27-1.36 8.69-1.44 13.94-1.52c9.76-.15 20.82-.31 28.51-8s7.85-18.75 8-28.51c.08-5.25.16-10.67 1.52-13.94c1.47-3.56 5.37-7.63 9.14-11.57c6.63-6.9 14.14-14.74 14.14-25.18s-7.51-18.27-14.14-25.18m-11.55 39.29c-4.79 5-9.75 10.17-12.38 16.52c-2.52 6.1-2.63 13.07-2.73 19.82c-.1 7-.21 14.33-3.32 17.43s-10.39 3.22-17.43 3.32c-6.75.1-13.72.21-19.82 2.73c-6.35 2.63-11.52 7.59-16.52 12.38S132 224 128 224s-9.15-4.92-14.11-9.69s-10.17-9.75-16.52-12.38c-6.1-2.52-13.07-2.63-19.82-2.73c-7-.1-14.33-.21-17.43-3.32s-3.22-10.39-3.32-17.43c-.1-6.75-.21-13.72-2.73-19.82c-2.63-6.35-7.59-11.52-12.38-16.52S32 132 32 128s4.92-9.15 9.69-14.11s9.75-10.17 12.38-16.52c2.52-6.1 2.63-13.07 2.73-19.82c.1-7 .21-14.33 3.32-17.43s10.39-3.22 17.43-3.32c6.75-.1 13.72-.21 19.82-2.73c6.35-2.63 11.52-7.59 16.52-12.38S124 32 128 32s9.15 4.92 14.11 9.69s10.17 9.75 16.52 12.38c6.1 2.52 13.07 2.63 19.82 2.73c7 .1 14.33.21 17.43 3.32s3.22 10.39 3.32 17.43c.1 6.75.21 13.72 2.73 19.82c2.63 6.35 7.59 11.52 12.38 16.52S224 124 224 128s-4.92 9.15-9.69 14.11m-40.65-43.77a8 8 0 0 1 0 11.32l-56 56a8 8 0 0 1-11.32 0l-24-24a8 8 0 0 1 11.32-11.32L112 148.69l50.34-50.35a8 8 0 0 1 11.32 0'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph\\:text-bolder{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M170.5 115.7A44 44 0 0 0 140 40H64a7.9 7.9 0 0 0-8 8v152a8 8 0 0 0 8 8h88a48 48 0 0 0 18.5-92.3ZM72 56h68a28 28 0 0 1 0 56H72Zm80 136H72v-64h80a32 32 0 0 1 0 64Z'/%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.i-ph\\:x-circle-duotone{--un-hub-icon:url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 256 256' width='1.11em' height='1.11em' xmlns='http://www.w3.org/2000/svg' %3E%3Cg fill='currentColor'%3E%3Cpath d='M224 128a96 96 0 1 1-96-96a96 96 0 0 1 96 96' opacity='.2'/%3E%3Cpath d='M165.66 101.66L139.31 128l26.35 26.34a8 8 0 0 1-11.32 11.32L128 139.31l-26.34 26.35a8 8 0 0 1-11.32-11.32L116.69 128l-26.35-26.34a8 8 0 0 1 11.32-11.32L128 116.69l26.34-26.35a8 8 0 0 1 11.32 11.32M232 128A104 104 0 1 1 128 24a104.11 104.11 0 0 1 104 104m-16 0a88 88 0 1 0-88 88a88.1 88.1 0 0 0 88-88'/%3E%3C/g%3E%3C/svg%3E");-webkit-mask:var(--un-hub-icon) no-repeat;mask:var(--un-hub-icon) no-repeat;color:inherit;background-color:currentColor;width:1.11em;height:1.11em;-webkit-mask-size:100% 100%;mask-size:100% 100%}.container{width:100%}.btn-action:disabled,.btn-action-sm:disabled{pointer-events:none;opacity:.3!important}.btn-icon:disabled,.btn-icon-compact:disabled{pointer-events:none;opacity:.3}.btn-primary:disabled{pointer-events:none;opacity:.5}.btn-text:disabled{pointer-events:none;opacity:.3!important}.z-command-palette{z-index:2147483646}.z-dock-confirm,.z-dock-toast{z-index:2147483647}.z-drawer-backdrop{z-index:80}.z-drawer-content{z-index:90}.z-dropdown{z-index:40}.z-floating-anchor{z-index:2147483644}.z-floating-tooltip{z-index:2147483645}.z-modal-backdrop{z-index:60}.z-modal-content{z-index:70}.z-nav{z-index:30}.z-toast{z-index:50}.btn-icon{opacity:.65;outline-offset:2px;border-radius:9999px;outline:2px solid #0000;justify-content:center;align-items:center;width:2.25rem;height:2.25rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:flex}.btn-icon-compact{opacity:.65;outline-offset:2px;border-radius:.25rem;outline:2px solid #0000;justify-content:center;align-items:center;width:1.5rem;height:1.5rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:flex}.btn-action{--un-hub-border-opacity:.13;border-width:1px;border-color:rgb(153 153 153/var(--un-hub-border-opacity));opacity:.75;outline-offset:2px;border-radius:.25rem;outline:2px solid #0000;align-items:center;gap:.5rem;padding:.25rem .5rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:flex}.btn-action-sm{--un-hub-border-opacity:.13;border-width:1px;border-color:rgb(153 153 153/var(--un-hub-border-opacity));opacity:.75;outline-offset:2px;border-radius:.25rem;outline:2px solid #0000;align-items:center;gap:.5rem;padding:.25rem .5rem;font-size:.875rem;line-height:1.25rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:flex}.btn-primary{--un-hub-bg-opacity:1;background-color:rgb(from var(--colors-primary-500,#578861) r g b / var(--un-hub-bg-opacity));--un-hub-text-opacity:1;color:rgb(255 255 255/var(--un-hub-text-opacity));outline-offset:2px;border-width:1px;border-color:#0000;border-radius:.25rem;outline:2px solid #0000;align-items:center;gap:.5rem;padding:.25rem .5rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:flex}.badge{border-radius:.375rem;align-items:center;gap:.25rem;padding:.125rem .375rem;font-size:.75rem;font-weight:500;line-height:1;display:inline-flex}.btn-text{opacity:.75;outline-offset:2px;border-width:1px;border-color:#0000;border-radius:.25rem;outline:2px solid #0000;align-items:center;gap:.5rem;padding:.25rem .5rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:inline-flex}.border-mute{--un-hub-border-opacity:.07;border-color:rgb(153 153 153/var(--un-hub-border-opacity))}.dark .data-\\[selected\\]\\:btn-action-active[data-selected],.dark .data-\\[state\\=on\\]\\:btn-action-active[data-state=on]{--un-hub-text-opacity:1;color:rgb(from var(--colors-primary-300,#93c69d) r g b / var(--un-hub-text-opacity));border-color:rgb(from var(--colors-primary-400,#75a67e) r g b / .25)!important}.data-\\[selected\\]\\:btn-action-active[data-selected],.data-\\[state\\=on\\]\\:btn-action-active[data-state=on]{--un-hub-bg-opacity:.19;background-color:rgb(153 153 153/var(--un-hub-bg-opacity));--un-hub-text-opacity:1;color:rgb(from var(--colors-primary-600,#40704b) r g b / var(--un-hub-text-opacity));border-color:rgb(from var(--colors-primary-600,#40704b) r g b / .25)!important;opacity:1!important}.hover\\:border-base:hover{--un-hub-border-opacity:.13;border-color:rgb(153 153 153/var(--un-hub-border-opacity))}.bg-active,.data-\\[state\\=checked\\]\\:bg-active[data-state=checked],.data-\\[state\\=open\\]\\:bg-active[data-state=open]{--un-hub-bg-opacity:.19;background-color:rgb(153 153 153/var(--un-hub-bg-opacity))}.bg-ambient{--un-hub-bg-opacity:.15;background-color:rgb(153 153 153/var(--un-hub-bg-opacity))}.bg-dock-glass,.bg-glass,.bg-toast-glass{--un-hub-backdrop-blur:blur(7px);backdrop-filter:var(--un-hub-backdrop-blur) var(--un-hub-backdrop-brightness) var(--un-hub-backdrop-contrast) var(--un-hub-backdrop-grayscale) var(--un-hub-backdrop-hue-rotate) var(--un-hub-backdrop-invert) var(--un-hub-backdrop-opacity) var(--un-hub-backdrop-saturate) var(--un-hub-backdrop-sepia);background-color:#ffffffa6}.dark .bg-dock-glass,.dark .bg-toast-glass{background-color:#111c}.dark .bg-glass{background-color:#11111180}.bg-glass\\:75{--un-hub-backdrop-blur:blur(7px);backdrop-filter:var(--un-hub-backdrop-blur) var(--un-hub-backdrop-brightness) var(--un-hub-backdrop-contrast) var(--un-hub-backdrop-grayscale) var(--un-hub-backdrop-hue-rotate) var(--un-hub-backdrop-invert) var(--un-hub-backdrop-opacity) var(--un-hub-backdrop-saturate) var(--un-hub-backdrop-sepia);background-color:#fffffffa}.dark .bg-glass\\:75{background-color:#111111bf}.bg-glass\\:80{--un-hub-backdrop-blur:blur(7px);backdrop-filter:var(--un-hub-backdrop-blur) var(--un-hub-backdrop-brightness) var(--un-hub-backdrop-contrast) var(--un-hub-backdrop-grayscale) var(--un-hub-backdrop-hue-rotate) var(--un-hub-backdrop-invert) var(--un-hub-backdrop-opacity) var(--un-hub-backdrop-saturate) var(--un-hub-backdrop-sepia);background-color:#fff}.dark .bg-glass\\:80{background-color:#111c}.bg-hover,.data-\\[highlighted\\]\\:bg-hover[data-highlighted]{--un-hub-bg-opacity:.13;background-color:rgb(153 153 153/var(--un-hub-bg-opacity))}.bg-raised,.data-\\[state\\=active\\]\\:bg-raised[data-state=active],.data-\\[state\\=on\\]\\:bg-raised[data-state=on]{background-color:#ffffffa6}.dark .bg-raised,.dark .data-\\[state\\=active\\]\\:bg-raised[data-state=active],.dark .data-\\[state\\=on\\]\\:bg-raised[data-state=on]{background-color:#ffffff0f}.bg-sunken{background-color:#0000000a}.dark .bg-sunken{background-color:#0003}.hover\\:bg-active:hover{--un-hub-bg-opacity:.19;background-color:rgb(153 153 153/var(--un-hub-bg-opacity))}.hover\\:bg-hover:hover{--un-hub-bg-opacity:.13;background-color:rgb(153 153 153/var(--un-hub-bg-opacity))}.bg-toast-glass:hover{background-color:#ffffffc7}.dark .bg-toast-glass:hover{background-color:#111111e6}.btn-action:hover,.btn-action-sm:hover,.btn-icon:hover,.btn-icon-compact:hover{--un-hub-bg-opacity:.13;background-color:rgb(153 153 153/var(--un-hub-bg-opacity));opacity:1}.btn-primary:hover{--un-hub-bg-opacity:1;background-color:rgb(from var(--colors-primary-600,#40704b) r g b / var(--un-hub-bg-opacity))}.btn-text:hover{--un-hub-bg-opacity:.13;background-color:rgb(153 153 153/var(--un-hub-bg-opacity));opacity:1}.bg-gradient-more{--un-hub-gradient-from-position:0%;--un-hub-gradient-from:rgb(255 255 255/var(--un-hub-from-opacity,1)) var(--un-hub-gradient-from-position);--un-hub-gradient-to-position:100%;--un-hub-gradient-to:#fff0;--un-hub-gradient-stops:var(--un-hub-gradient-from), #fffc var(--un-hub-gradient-via-position), var(--un-hub-gradient-to);--un-hub-gradient-via-position:50%;--un-hub-gradient-shape:to top in oklch;--un-hub-gradient:var(--un-hub-gradient-shape), var(--un-hub-gradient-stops);background-image:linear-gradient(var(--un-hub-gradient))}.dark .bg-gradient-more{--un-hub-gradient-from-position:0%;--un-hub-gradient-from:rgb(17 17 17/var(--un-hub-from-opacity,1)) var(--un-hub-gradient-from-position);--un-hub-gradient-to-position:100%;--un-hub-gradient-to:#1110;--un-hub-gradient-stops:var(--un-hub-gradient-from), #111c var(--un-hub-gradient-via-position), var(--un-hub-gradient-to);--un-hub-gradient-via-position:50%}.text-micro{font-size:10px;line-height:1.4}.color-active,.data-\\[state\\=active\\]\\:color-active[data-state=active],.data-\\[state\\=checked\\]\\:color-active[data-state=checked]{--un-hub-text-opacity:1;color:rgb(from var(--colors-primary-600,#40704b) r g b / var(--un-hub-text-opacity))}.dark .color-active,.dark .data-\\[state\\=active\\]\\:color-active[data-state=active],.dark .data-\\[state\\=checked\\]\\:color-active[data-state=checked]{--un-hub-text-opacity:1;color:rgb(from var(--colors-primary-300,#93c69d) r g b / var(--un-hub-text-opacity))}.color-base,.data-\\[state\\=active\\]\\:color-base[data-state=active],.data-\\[state\\=on\\]\\:color-base[data-state=on]{--un-hub-text-opacity:1;color:rgb(38 38 38/var(--un-hub-text-opacity))}.dark .color-base,.dark .data-\\[state\\=active\\]\\:color-base[data-state=active],.dark .data-\\[state\\=on\\]\\:color-base[data-state=on]{--un-hub-text-opacity:1;color:rgb(229 229 229/var(--un-hub-text-opacity))}.dark .hover\\:color-active:hover{--un-hub-text-opacity:1;color:rgb(from var(--colors-primary-300,#93c69d) r g b / var(--un-hub-text-opacity))}.hover\\:color-active:hover{--un-hub-text-opacity:1;color:rgb(from var(--colors-primary-600,#40704b) r g b / var(--un-hub-text-opacity))}.dark .hover\\:color-base:hover{--un-hub-text-opacity:1;color:rgb(229 229 229/var(--un-hub-text-opacity))}.hover\\:color-base:hover{--un-hub-text-opacity:1;color:rgb(38 38 38/var(--un-hub-text-opacity))}.dark .btn-icon,.dark .btn-icon-compact,.dark .op-fade{opacity:.55}.op-fade{opacity:.65}.dark .op-mute{opacity:.25}.op-mute{opacity:.3}.dark .disabled\\:op-mute:disabled{opacity:.25}.disabled\\:op-mute:disabled{opacity:.3}.dark .placeholder\\:op-mute::placeholder{opacity:.25}.placeholder\\:op-mute::placeholder{opacity:.3}.btn-action:focus-visible,.btn-action-sm:focus-visible,.btn-icon:focus-visible,.btn-icon-compact:focus-visible,.btn-primary:focus-visible,.btn-text:focus-visible{--un-hub-ring-width:2px;--un-hub-ring-offset-shadow:var(--un-hub-ring-inset) 0 0 0 var(--un-hub-ring-offset-width) var(--un-hub-ring-offset-color);--un-hub-ring-shadow:var(--un-hub-ring-inset) 0 0 0 calc(var(--un-hub-ring-width) + var(--un-hub-ring-offset-width)) var(--un-hub-ring-color);box-shadow:var(--un-hub-ring-offset-shadow), var(--un-hub-ring-shadow), var(--un-hub-shadow);--un-hub-ring-color:rgb(from var(--colors-primary-500,#578861) r g b / .4)}.dark .icon-catppuccin{--un-hub-brightness:brightness(1);filter:var(--un-hub-blur) var(--un-hub-brightness) var(--un-hub-contrast) var(--un-hub-drop-shadow) var(--un-hub-grayscale) var(--un-hub-hue-rotate) var(--un-hub-invert) var(--un-hub-saturate) var(--un-hub-sepia);--un-hub-hue-rotate:hue-rotate(0deg);--un-hub-invert:invert(0)}.icon-catppuccin{--un-hub-brightness:brightness(.8);filter:var(--un-hub-blur) var(--un-hub-brightness) var(--un-hub-contrast) var(--un-hub-drop-shadow) var(--un-hub-grayscale) var(--un-hub-hue-rotate) var(--un-hub-invert) var(--un-hub-saturate) var(--un-hub-sepia);--un-hub-hue-rotate:hue-rotate(180deg);--un-hub-invert:invert(1)}@media (width>=640px){.container{max-width:640px}}@media (width>=768px){.container{max-width:768px}}@media (width>=1024px){.container{max-width:1024px}}@media (width>=1280px){.container{max-width:1280px}}@media (width>=1536px){.container{max-width:1536px}}@property --scroll-fade-t{syntax:"";inherits:false;initial-value:0}@property --scroll-fade-b{syntax:"";inherits:false;initial-value:0}@property --scroll-fade-s{syntax:"";inherits:false;initial-value:0}@property --scroll-fade-e{syntax:"";inherits:false;initial-value:0}@property --scroll-fade-mask{syntax:"*";inherits:false}@property --shimmer-angle{syntax:"";inherits:true;initial-value:20deg}@property --shimmer-image{syntax:"*";inherits:false}@property --shimmer-text-fill{syntax:"*";inherits:false}.bg-dots{background-image:radial-gradient(#88888840 1px,#0000 1px);background-size:16px 16px}.bg-grid{background-image:linear-gradient(90deg,#88888826 1px,#0000 1px),linear-gradient(#88888826 1px,#0000 1px);background-size:16px 16px}.shimmer{--_spread:var(--shimmer-spread,calc(3ch + 40px));--_base:currentColor;--_highlight:var(--shimmer-color,oklch(from currentColor l c h / calc(alpha * .2)));background-image:var(--shimmer-image,linear-gradient(calc(90deg + var(--shimmer-angle)),var(--_base) calc(50% - var(--_spread)),color-mix(in oklch,var(--_highlight),var(--_base) 50%) calc(50% - var(--_spread) * .5),var(--_highlight) 50%,color-mix(in oklch,var(--_highlight),var(--_base) 50%) calc(50% + var(--_spread) * .5),var(--_base) calc(50% + var(--_spread))));background-repeat:no-repeat;background-size:calc(200% + var(--_spread) * 2) 100%;-webkit-text-fill-color:var(--shimmer-text-fill,transparent);animation:tw-shimmer var(--shimmer-duration,2s) linear infinite;background-position:0 0;-webkit-background-clip:text;background-clip:text}:where(html.dark) .shimmer{--_highlight:var(--shimmer-color,oklch(from currentColor max(.8,calc(l + .4)) c h / calc(alpha + .4)))}.shimmer:where([dir=rtl],[dir=rtl] *){animation-direction:reverse}@keyframes tw-shimmer{0%{background-position:100% 0}to{background-position:0 0}}@media (prefers-reduced-motion:reduce){.shimmer{-webkit-text-fill-color:currentColor;background-image:none;animation:none}}.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.pointer-events-auto{pointer-events:auto}.data-\\[disabled\\]\\:pointer-events-none[data-disabled],.pointer-events-none,.disabled\\:pointer-events-none:disabled{pointer-events:none}.visible{visibility:visible}.invisible{visibility:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.static{position:static}.inset-0{inset:0}.inset-x-0{left:0;right:0}.bottom-0{bottom:0}.bottom-4{bottom:1rem}.bottom-4px{bottom:4px}.left-0{left:0}.left-1\\.5{left:.375rem}.left-1\\/2{left:50%}.left-2{left:.5rem}.left-2\\.5{left:.625rem}.left-4{left:1rem}.left-5px{left:5px}.right--1px{right:-1px}.right-0{right:0}.right-0\\.5{right:.125rem}.right-2{right:.5rem}.right-4{right:1rem}.top--32px{top:-32px}.top-0{top:0}.top-0\\.5{top:.125rem}.top-1{top:.25rem}.top-1\\/2{top:50%}.top-4{top:1rem}.top-4px{top:4px}.grid{display:grid}.cols-\\[max-content_1fr\\]{grid-template-columns:max-content 1fr}.m-auto,.ma{margin:auto}.m1{margin:.25rem}.mx--1{margin-left:-.25rem;margin-right:-.25rem}.mx--2{margin-left:-.5rem;margin-right:-.5rem}.mx-1{margin-left:.25rem;margin-right:.25rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-auto{margin-left:auto;margin-right:auto}.my{margin-top:1rem;margin-bottom:1rem}.my-1,.my1{margin-top:.25rem;margin-bottom:.25rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my0\\.5{margin-top:.125rem;margin-bottom:.125rem}.-mb-px{margin-bottom:-1px}.mb-1{margin-bottom:.25rem}.mb-1\\.5{margin-bottom:.375rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.ml-0\\.5{margin-left:.125rem}.ml-1{margin-left:.25rem}.ml-1\\.5{margin-left:.375rem}.ml-2{margin-left:.5rem}.ml-6{margin-left:1.5rem}.mr-1{margin-right:.25rem}.ms{margin-inline-start:1rem}.mt--1px{margin-top:-1px}.mt-0\\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\\.5{margin-top:.375rem}.mt-2,.mt2{margin-top:.5rem}.mt-4,.mt4{margin-top:1rem}.mt-6,.mt6{margin-top:1.5rem}.mt-auto{margin-top:auto}.box-border{box-sizing:border-box}.inline{display:inline}.block{display:block}.inline-block{display:inline-block}.contents{display:contents}.dark .dark-hidden,.hidden,.light .light-hidden{display:none}.\\[\\&\\>svg\\]\\:h-full>svg,.h-full{height:100%}.\\[\\&\\>svg\\]\\:w-full>svg,.w-full{width:100%}.h-0\\.5{height:.125rem}.h-1{height:.25rem}.h-1\\.5{height:.375rem}.h-1\\/3{height:33.3333%}.h-10{height:2.5rem}.h-14{height:3.5rem}.h-16{height:4rem}.h-2{height:.5rem}.h-20px{height:20px}.h-24{height:6rem}.h-3{height:.75rem}.h-3\\.5{height:.875rem}.h-4{height:1rem}.h-4\\.5{height:1.125rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-auto\\!{height:auto!important}.h-px{height:1px}.h-screen{height:100vh}.max-h-\\[60vh\\]{max-height:60vh}.max-h-24{max-height:6rem}.max-h-full{max-height:100%}.max-w-\\[90vw\\]{max-width:90vw}.max-w-\\[calc\\(100vw-2rem\\)\\]{max-width:calc(100vw - 2rem)}.max-w-100{max-width:25rem}.max-w-108{max-width:27rem}.max-w-200{max-width:50rem}.max-w-220px{max-width:220px}.max-w-64{max-width:16rem}.max-w-92{max-width:23rem}.max-w-96,.max-w-sm{max-width:24rem}.max-w-full{max-width:100%}.max-w-lg{max-width:32rem}.max-w-prose{max-width:65ch}.max-w-xs{max-width:20rem}.min-h-0{min-height:0}.min-h-16{min-height:4rem}.min-h-40{min-height:10rem}.min-h-5{min-height:1.25rem}.min-w-\\[--reka-combobox-trigger-width\\]{min-width:var(--reka-combobox-trigger-width)}.min-w-\\[--reka-select-trigger-width\\]{min-width:var(--reka-select-trigger-width)}.min-w-0{min-width:0}.min-w-28{min-width:7rem}.min-w-36{min-width:9rem}.min-w-40{min-width:10rem}.min-w-44{min-width:11rem}.min-w-5{min-width:1.25rem}.min-w-6{min-width:1.5rem}.w-\\[--reka-tabs-indicator-size\\]{width:var(--reka-tabs-indicator-size)}.w-\\[40px\\]{width:40px}.w-1\\.5{width:.375rem}.w-10{width:2.5rem}.w-11{width:2.75rem}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-2\\/5{width:40%}.w-20px{width:20px}.w-24{width:6rem}.w-2px{width:2px}.w-3{width:.75rem}.w-3\\.5{width:.875rem}.w-3\\/5{width:60%}.w-4{width:1rem}.w-4\\.5{width:1.125rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-7{width:1.75rem}.w-72{width:18rem}.w-8{width:2rem}.w-8\\.5{width:2.125rem}.w-80{width:20rem}.w-96{width:24rem}.w-auto\\!{width:auto!important}.w-fit{width:fit-content}.w-lg{width:32rem}.w-max{width:max-content}.w-px{width:1px}.w-screen{width:100vw}.flex{display:flex}.inline-flex{display:inline-flex}.flex-1{flex:1}.flex-auto{flex:auto}.flex-none{flex:none}.shrink-0{flex-shrink:0}.shrink-to-fit{flex-shrink:1}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.table{display:table}.border-collapse{border-collapse:collapse}.-translate-x-1\\/2,.translate-x--1\\/2{--un-hub-translate-x:-50%;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.-translate-y-2{--un-hub-translate-y:-.5rem;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.data-\\[state\\=checked\\]\\:translate-x-4[data-state=checked],.translate-x-4{--un-hub-translate-x:1rem;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.translate-x-\\[--reka-tabs-indicator-position\\]{--un-hub-translate-x:var(--reka-tabs-indicator-position);transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.translate-x-0\\.5{--un-hub-translate-x:.125rem;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.translate-x-1{--un-hub-translate-x:.25rem;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.translate-x-5{--un-hub-translate-x:1.25rem;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.translate-y--1\\/2{--un-hub-translate-y:-50%;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.translate-y-0{--un-hub-translate-y:0;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.translate-y-2{--un-hub-translate-y:.5rem;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.-rotate-90{--un-hub-rotate-x:0;--un-hub-rotate-y:0;--un-hub-rotate-z:0;--un-hub-rotate:-90deg;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.group[data-state=open] .group-data-\\[state\\=open\\]\\:rotate-90,.rotate-90{--un-hub-rotate-x:0;--un-hub-rotate-y:0;--un-hub-rotate-z:0;--un-hub-rotate:90deg;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.rotate--45{--un-hub-rotate-x:0;--un-hub-rotate-y:0;--un-hub-rotate-z:0;--un-hub-rotate:-45deg;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.rotate-0{--un-hub-rotate-x:0;--un-hub-rotate-y:0;--un-hub-rotate-z:0;--un-hub-rotate:0deg;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.rotate-180{--un-hub-rotate-x:0;--un-hub-rotate-y:0;--un-hub-rotate-z:0;--un-hub-rotate:180deg;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.rotate-270{--un-hub-rotate-x:0;--un-hub-rotate-y:0;--un-hub-rotate-z:0;--un-hub-rotate:270deg;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.scale-100{--un-hub-scale-x:1;--un-hub-scale-y:1;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.scale-120{--un-hub-scale-x:1.2;--un-hub-scale-y:1.2;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.scale-98{--un-hub-scale-x:.98;--un-hub-scale-y:.98;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.hover\\:scale-110:hover{--un-hub-scale-x:1.1;--un-hub-scale-y:1.1;transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}.transform{transform:translateX(var(--un-hub-translate-x)) translateY(var(--un-hub-translate-y)) translateZ(var(--un-hub-translate-z)) rotate(var(--un-hub-rotate)) rotateX(var(--un-hub-rotate-x)) rotateY(var(--un-hub-rotate-y)) rotateZ(var(--un-hub-rotate-z)) skewX(var(--un-hub-skew-x)) skewY(var(--un-hub-skew-y)) scaleX(var(--un-hub-scale-x)) scaleY(var(--un-hub-scale-y)) scaleZ(var(--un-hub-scale-z))}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.animate-pulse{animation:2s cubic-bezier(.4,0,.6,1) infinite pulse}.animate-spin{animation:1s linear infinite spin}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-not-allowed{cursor:not-allowed}.cursor-grab{cursor:grab}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;user-select:none}.resize{resize:both}.resize-none{resize:none}.items-start{align-items:flex-start}.items-center{align-items:center}.self-stretch{align-self:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0{gap:0}.gap-0\\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-x-2{column-gap:.5rem}.gap-y-0\\.5{row-gap:.125rem}.space-y-0\\.5>:not([hidden])~:not([hidden]){--un-hub-space-y-reverse:0;margin-top:calc(.125rem * calc(1 - var(--un-hub-space-y-reverse)));margin-bottom:calc(.125rem * var(--un-hub-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--un-hub-divide-y-reverse:0;border-top-width:calc(1px * calc(1 - var(--un-hub-divide-y-reverse)));border-bottom-width:calc(1px * var(--un-hub-divide-y-reverse))}.divide-\\#8882>:not([hidden])~:not([hidden]){--un-hub-divide-opacity:.13;border-color:rgb(136 136 136/var(--un-hub-divide-opacity))}.of-hidden,.overflow-hidden{overflow:hidden}.overflow-auto{overflow:auto}.overflow-clip{overflow:clip}.of-x-hidden{overflow-x:hidden}.of-y-auto{overflow-y:auto}.of-y-hidden{overflow-y:hidden}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.whitespace-pre-wrap{white-space:pre-wrap}.ws-nowrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.b,.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-b-1\\.5{border-bottom-width:1.5px}.border-b-2{border-bottom-width:2px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-r-1\\.5{border-right-width:1.5px}.border-t{border-top-width:1px}.border-t-0{border-top-width:0}.first\\:border-l-0:first-child{border-left-width:0}.last\\:border-b-0:last-child{border-bottom-width:0}.border-amber-500\\/20{border-color:#f59e0b33}.border-amber\\/20{border-color:#fbbf2433}.border-blue-500\\/20{border-color:#3b82f633}.border-current{border-color:currentColor}.border-error\\!{--un-hub-border-opacity:1!important;border-color:rgb(246 61 104/var(--un-hub-border-opacity))!important}.border-green-500\\/20{border-color:#22c55e33}.border-primary-400\\/60{border-color:rgb(from var(--colors-primary-400,#75a67e) r g b / .6)}.border-primary\\/30{border-color:rgb(from var(--colors-primary-DEFAULT,#3a6a45) r g b / .3)}.border-red-500\\/20{border-color:#ef444433}.border-red-500\\/60{border-color:#ef444499}.border-red-500\\/70{border-color:#ef4444b3}.border-transparent{border-color:#0000}.dark .dark\\:border-primary-400\\/50{border-color:rgb(from var(--colors-primary-400,#75a67e) r g b / .5)}.dark .dark\\:data-\\[state\\=active\\]\\:border-primary-400[data-state=active]{--un-hub-border-opacity:1;border-color:rgb(from var(--colors-primary-400,#75a67e) r g b / var(--un-hub-border-opacity))}.data-\\[state\\=active\\]\\:border-primary-500[data-state=active],.data-\\[state\\=checked\\]\\:border-primary-500[data-state=checked]{--un-hub-border-opacity:1;border-color:rgb(from var(--colors-primary-500,#578861) r g b / var(--un-hub-border-opacity))}.focus-within\\:border-gray\\/15:focus-within{border-color:#9ca3af26}.hover\\:border-gray\\/10:hover{border-color:#9ca3af1a}.focus\\:border-primary-500:focus{--un-hub-border-opacity:1;border-color:rgb(from var(--colors-primary-500,#578861) r g b / var(--un-hub-border-opacity))}.focus\\:border-primary\\/40:focus{border-color:rgb(from var(--colors-primary-DEFAULT,#3a6a45) r g b / .4)}.border-t-transparent{border-top-color:#0000}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.bg-\\[\\#8881\\],.bg-\\#8881,.data-\\[state\\=visible\\]\\:bg-\\#8881[data-state=visible]{--un-hub-bg-opacity:.07;background-color:rgb(136 136 136/var(--un-hub-bg-opacity))}.bg-\\[\\#ddd\\]\\/40{background-color:#ddd6}.bg-\\#8882{--un-hub-bg-opacity:.13;background-color:rgb(136 136 136/var(--un-hub-bg-opacity))}.bg-\\#8885{--un-hub-bg-opacity:.33;background-color:rgb(136 136 136/var(--un-hub-bg-opacity))}.bg-amber{--un-hub-bg-opacity:1;background-color:rgb(251 191 36/var(--un-hub-bg-opacity))}.bg-amber-400\\/10,.bg-amber\\/10{background-color:#fbbf241a}.bg-black\\/30,.dark .dark\\:bg-black\\/30{background-color:#0000004d}.bg-blue{--un-hub-bg-opacity:1;background-color:rgb(96 165 250/var(--un-hub-bg-opacity))}.bg-blue-400\\/10,.bg-blue\\/10{background-color:#60a5fa1a}.bg-current{background-color:currentColor}.bg-error\\!{--un-hub-bg-opacity:1!important;background-color:rgb(246 61 104/var(--un-hub-bg-opacity))!important}.bg-gray{--un-hub-bg-opacity:1;background-color:rgb(156 163 175/var(--un-hub-bg-opacity))}.bg-gray-500{--un-hub-bg-opacity:1;background-color:rgb(107 114 128/var(--un-hub-bg-opacity))}.bg-gray-6{--un-hub-bg-opacity:1;background-color:rgb(75 85 99/var(--un-hub-bg-opacity))}.bg-gray\\/10{background-color:#9ca3af1a}.bg-gray\\/20{background-color:#9ca3af33}.bg-gray\\/3{background-color:#9ca3af08}.bg-gray\\/30{background-color:#9ca3af4d}.bg-gray\\/5{background-color:#9ca3af0d}.bg-green{--un-hub-bg-opacity:1;background-color:rgb(74 222 128/var(--un-hub-bg-opacity))}.bg-green-400\\/10{background-color:#4ade801a}.bg-orange\\/10{background-color:#fb923c1a}.bg-primary{--un-hub-bg-opacity:1;background-color:rgb(from var(--colors-primary-DEFAULT,#3a6a45) r g b / var(--un-hub-bg-opacity))}.bg-primary-500,.data-\\[state\\=checked\\]\\:bg-primary-500[data-state=checked]{--un-hub-bg-opacity:1;background-color:rgb(from var(--colors-primary-500,#578861) r g b / var(--un-hub-bg-opacity))}.bg-primary-500\\/20{background-color:rgb(from var(--colors-primary-500,#578861) r g b / .2)}.bg-primary\\/10{background-color:rgb(from var(--colors-primary-DEFAULT,#3a6a45) r g b / .1)}.bg-primary\\/15{background-color:rgb(from var(--colors-primary-DEFAULT,#3a6a45) r g b / .15)}.bg-primary\\/20{background-color:rgb(from var(--colors-primary-DEFAULT,#3a6a45) r g b / .2)}.bg-red{--un-hub-bg-opacity:1;background-color:rgb(248 113 113/var(--un-hub-bg-opacity))}.bg-red-400\\/10,.bg-red\\/10{background-color:#f871711a}.bg-red-500\\!{--un-hub-bg-opacity:1!important;background-color:rgb(239 68 68/var(--un-hub-bg-opacity))!important}.bg-transparent{background-color:#0000}.bg-white{--un-hub-bg-opacity:1;background-color:rgb(255 255 255/var(--un-hub-bg-opacity))}.bg-white\\/50{background-color:#ffffff80}.dark .dark\\:bg-black\\/40{background-color:#0006}.dark .dark\\:bg-black\\/45{background-color:#00000073}.data-\\[highlighted\\]\\:bg-red-500\\/10[data-highlighted]{background-color:#ef44441a}.hover\\:bg-\\[\\#8881\\]:hover{--un-hub-bg-opacity:.07;background-color:rgb(136 136 136/var(--un-hub-bg-opacity))}.hover\\:bg-\\[\\#8882\\]:hover{--un-hub-bg-opacity:.13;background-color:rgb(136 136 136/var(--un-hub-bg-opacity))}.hover\\:bg-\\#8888:hover{--un-hub-bg-opacity:.53;background-color:rgb(136 136 136/var(--un-hub-bg-opacity))}.hover\\:bg-gray\\/10:hover{background-color:#9ca3af1a}.hover\\:bg-gray\\/15:hover{background-color:#9ca3af26}.hover\\:bg-gray\\/20:hover{background-color:#9ca3af33}.hover\\:bg-gray\\/5:hover{background-color:#9ca3af0d}.hover\\:bg-orange\\/20:hover{background-color:#fb923c33}.hover\\:bg-primary\\/25:hover{background-color:rgb(from var(--colors-primary-DEFAULT,#3a6a45) r g b / .25)}.hover\\:bg-red-600\\!:hover{--un-hub-bg-opacity:1!important;background-color:rgb(220 38 38/var(--un-hub-bg-opacity))!important}.hover\\:bg-red\\/20:hover{background-color:#f8717133}.stroke-current{stroke:currentColor}.object-contain{object-fit:contain}.p-\\[0\\.25em\\]{padding:.25em}.p-\\[0\\.5em\\]{padding:.5em}.p-0\\.5{padding:.125rem}.p-1,.p1{padding:.25rem}.p-1\\.5,.p1\\.5{padding:.375rem}.p-2,.p2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p6{padding:1.5rem}.p8{padding:2rem}.px,.px-4,.px4{padding-left:1rem;padding-right:1rem}.px-0\\.5{padding-left:.125rem;padding-right:.125rem}.px-1,.px1{padding-left:.25rem;padding-right:.25rem}.px-1\\.5{padding-left:.375rem;padding-right:.375rem}.px-2,.px2{padding-left:.5rem;padding-right:.5rem}.px-2\\.5{padding-left:.625rem;padding-right:.625rem}.px-3,.px3{padding-left:.75rem;padding-right:.75rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0{padding-top:0;padding-bottom:0}.py-0\\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1,.py1{padding-top:.25rem;padding-bottom:.25rem}.py-1\\.5,.py1\\.5{padding-top:.375rem;padding-bottom:.375rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2,.py2{padding-top:.5rem;padding-bottom:.5rem}.py-2\\!{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-2\\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb2\\.5{padding-bottom:.625rem}.pl-4{padding-left:1rem}.pl-5{padding-left:1.25rem}.pl-7{padding-left:1.75rem}.pl2\\.5{padding-left:.625rem}.pr-2{padding-right:.5rem}.pr1{padding-right:.25rem}.pt-\\[20vh\\]{padding-top:20vh}.pt-1\\.5{padding-top:.375rem}.pt-6{padding-top:1.5rem}.pt2{padding-top:.5rem}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.text-balance{text-wrap:balance}.text-\\[10px\\]{font-size:10px}.text-\\[15px\\]{font-size:15px}.text-0\\.6em{font-size:.6em}.text-2\\.75{font-size:.6875rem}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.dark .dark\\:text-amber-300{--un-hub-text-opacity:1;color:rgb(252 211 77/var(--un-hub-text-opacity))}.dark .dark\\:text-amber-400,.text-amber{--un-hub-text-opacity:1;color:rgb(251 191 36/var(--un-hub-text-opacity))}.dark .dark\\:text-blue-300{--un-hub-text-opacity:1;color:rgb(147 197 253/var(--un-hub-text-opacity))}.dark .dark\\:text-green-300{--un-hub-text-opacity:1;color:rgb(134 239 172/var(--un-hub-text-opacity))}.dark .dark\\:text-green-400,.text-green{--un-hub-text-opacity:1;color:rgb(74 222 128/var(--un-hub-text-opacity))}.dark .dark\\:text-primary-300{--un-hub-text-opacity:1;color:rgb(from var(--colors-primary-300,#93c69d) r g b / var(--un-hub-text-opacity))}.dark .dark\\:text-red-300{--un-hub-text-opacity:1;color:rgb(252 165 165/var(--un-hub-text-opacity))}.dark .dark\\:text-red-400,.text-red{--un-hub-text-opacity:1;color:rgb(248 113 113/var(--un-hub-text-opacity))}.text-amber-600{--un-hub-text-opacity:1;color:rgb(217 119 6/var(--un-hub-text-opacity))}.text-amber-700{--un-hub-text-opacity:1;color:rgb(180 83 9/var(--un-hub-text-opacity))}.text-blue{--un-hub-text-opacity:1;color:rgb(96 165 250/var(--un-hub-text-opacity))}.text-blue-700{--un-hub-text-opacity:1;color:rgb(29 78 216/var(--un-hub-text-opacity))}.text-gray{--un-hub-text-opacity:1;color:rgb(156 163 175/var(--un-hub-text-opacity))}.text-green-600{--un-hub-text-opacity:1;color:rgb(22 163 74/var(--un-hub-text-opacity))}.text-green-700{--un-hub-text-opacity:1;color:rgb(21 128 61/var(--un-hub-text-opacity))}.text-orange{--un-hub-text-opacity:1;color:rgb(251 146 60/var(--un-hub-text-opacity))}.text-primary,.text-primary\\/bg-active{--un-hub-text-opacity:1;color:rgb(from var(--colors-primary-DEFAULT,#3a6a45) r g b / var(--un-hub-text-opacity))}.text-primary-600{--un-hub-text-opacity:1;color:rgb(from var(--colors-primary-600,#40704b) r g b / var(--un-hub-text-opacity))}.text-red-500{--un-hub-text-opacity:1;color:rgb(239 68 68/var(--un-hub-text-opacity))}.text-red-600{--un-hub-text-opacity:1;color:rgb(220 38 38/var(--un-hub-text-opacity))}.text-red-700{--un-hub-text-opacity:1;color:rgb(185 28 28/var(--un-hub-text-opacity))}.text-white{--un-hub-text-opacity:1;color:rgb(255 255 255/var(--un-hub-text-opacity))}.font-bold{font-weight:700}.font-medium{font-weight:500}.leading-4{line-height:1rem}.leading-5{line-height:1.25rem}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.italic{font-style:italic}.tabular-nums{--un-hub-numeric-spacing:tabular-nums;font-variant-numeric:var(--un-hub-ordinal) var(--un-hub-slashed-zero) var(--un-hub-numeric-figure) var(--un-hub-numeric-spacing) var(--un-hub-numeric-fraction)}.line-through{text-decoration-line:line-through}.underline,.hover\\:underline:hover{text-decoration-line:underline}.tab{-moz-tab-size:4;-o-tab-size:4;tab-size:4}.caret-primary-500{--un-hub-caret-opacity:1;caret-color:rgb(from var(--colors-primary-500,#578861) r g b / var(--un-hub-caret-opacity))}.data-\\[disabled\\]\\:op50[data-disabled],.op50,.group:hover .group-hover\\:op50{opacity:.5}.data-\\[state\\=checked\\]\\:op100[data-state=checked],.op100,.opacity-100,.group:hover .group-hover\\:opacity-100{opacity:1}.op0,.opacity-0{opacity:0}.op25{opacity:.25}.op30{opacity:.3}.op40{opacity:.4}.op60{opacity:.6}.op60\\!{opacity:.6!important}.op70{opacity:.7}.op75{opacity:.75}.op80{opacity:.8}.hover\\:op100:hover{opacity:1}.hover\\:op60:hover{opacity:.6}.hover\\:op70:hover{opacity:.7}.hover\\:op80:hover{opacity:.8}.disabled\\:op40:disabled{opacity:.4}.disabled\\:op50:disabled{opacity:.5}.data-\\[state\\=active\\]\\:shadow-sm[data-state=active],.data-\\[state\\=on\\]\\:shadow-sm[data-state=on],.shadow-sm{--un-hub-shadow:var(--un-hub-shadow-inset) 0 1px 2px 0 var(--un-hub-shadow-color,#0000000d);box-shadow:var(--un-hub-ring-offset-shadow), var(--un-hub-ring-shadow), var(--un-hub-shadow)}.shadow{--un-hub-shadow:var(--un-hub-shadow-inset) 0 1px 3px 0 var(--un-hub-shadow-color,#0000001a),var(--un-hub-shadow-inset) 0 1px 2px -1px var(--un-hub-shadow-color,#0000001a);box-shadow:var(--un-hub-ring-offset-shadow), var(--un-hub-ring-shadow), var(--un-hub-shadow)}.shadow-2xl{--un-hub-shadow:var(--un-hub-shadow-inset) 0 25px 50px -12px var(--un-hub-shadow-color,#00000040);box-shadow:var(--un-hub-ring-offset-shadow), var(--un-hub-ring-shadow), var(--un-hub-shadow)}.shadow-lg{--un-hub-shadow:var(--un-hub-shadow-inset) 0 10px 15px -3px var(--un-hub-shadow-color,#0000001a),var(--un-hub-shadow-inset) 0 4px 6px -4px var(--un-hub-shadow-color,#0000001a);box-shadow:var(--un-hub-ring-offset-shadow), var(--un-hub-ring-shadow), var(--un-hub-shadow)}.shadow-xl{--un-hub-shadow:var(--un-hub-shadow-inset) 0 20px 25px -5px var(--un-hub-shadow-color,#0000001a),var(--un-hub-shadow-inset) 0 8px 10px -6px var(--un-hub-shadow-color,#0000001a);box-shadow:var(--un-hub-ring-offset-shadow), var(--un-hub-ring-shadow), var(--un-hub-shadow)}.outline{outline-style:solid}.outline-none{outline-offset:2px;outline:2px solid #0000}.ring-1\\.5{--un-hub-ring-width:1.5px;--un-hub-ring-offset-shadow:var(--un-hub-ring-inset) 0 0 0 var(--un-hub-ring-offset-width) var(--un-hub-ring-offset-color);--un-hub-ring-shadow:var(--un-hub-ring-inset) 0 0 0 calc(var(--un-hub-ring-width) + var(--un-hub-ring-offset-width)) var(--un-hub-ring-color);box-shadow:var(--un-hub-ring-offset-shadow), var(--un-hub-ring-shadow), var(--un-hub-shadow)}.focus-within\\:ring-2:focus-within{--un-hub-ring-width:2px;--un-hub-ring-offset-shadow:var(--un-hub-ring-inset) 0 0 0 var(--un-hub-ring-offset-width) var(--un-hub-ring-offset-color);--un-hub-ring-shadow:var(--un-hub-ring-inset) 0 0 0 calc(var(--un-hub-ring-width) + var(--un-hub-ring-offset-width)) var(--un-hub-ring-color);box-shadow:var(--un-hub-ring-offset-shadow), var(--un-hub-ring-shadow), var(--un-hub-shadow)}.focus\\:ring-1:focus{--un-hub-ring-width:1px;--un-hub-ring-offset-shadow:var(--un-hub-ring-inset) 0 0 0 var(--un-hub-ring-offset-width) var(--un-hub-ring-offset-color);--un-hub-ring-shadow:var(--un-hub-ring-inset) 0 0 0 calc(var(--un-hub-ring-width) + var(--un-hub-ring-offset-width)) var(--un-hub-ring-color);box-shadow:var(--un-hub-ring-offset-shadow), var(--un-hub-ring-shadow), var(--un-hub-shadow)}.focus\\:ring-2:focus{--un-hub-ring-width:2px;--un-hub-ring-offset-shadow:var(--un-hub-ring-inset) 0 0 0 var(--un-hub-ring-offset-width) var(--un-hub-ring-offset-color);--un-hub-ring-shadow:var(--un-hub-ring-inset) 0 0 0 calc(var(--un-hub-ring-width) + var(--un-hub-ring-offset-width)) var(--un-hub-ring-color);box-shadow:var(--un-hub-ring-offset-shadow), var(--un-hub-ring-shadow), var(--un-hub-shadow)}.focus\\:ring-3:focus{--un-hub-ring-width:3px;--un-hub-ring-offset-shadow:var(--un-hub-ring-inset) 0 0 0 var(--un-hub-ring-offset-width) var(--un-hub-ring-offset-color);--un-hub-ring-shadow:var(--un-hub-ring-inset) 0 0 0 calc(var(--un-hub-ring-width) + var(--un-hub-ring-offset-width)) var(--un-hub-ring-color);box-shadow:var(--un-hub-ring-offset-shadow), var(--un-hub-ring-shadow), var(--un-hub-shadow)}.focus-visible\\:ring-2:focus-visible{--un-hub-ring-width:2px;--un-hub-ring-offset-shadow:var(--un-hub-ring-inset) 0 0 0 var(--un-hub-ring-offset-width) var(--un-hub-ring-offset-color);--un-hub-ring-shadow:var(--un-hub-ring-inset) 0 0 0 calc(var(--un-hub-ring-width) + var(--un-hub-ring-offset-width)) var(--un-hub-ring-color);box-shadow:var(--un-hub-ring-offset-shadow), var(--un-hub-ring-shadow), var(--un-hub-shadow)}.focus-visible\\:ring-3:focus-visible{--un-hub-ring-width:3px;--un-hub-ring-offset-shadow:var(--un-hub-ring-inset) 0 0 0 var(--un-hub-ring-offset-width) var(--un-hub-ring-offset-color);--un-hub-ring-shadow:var(--un-hub-ring-inset) 0 0 0 calc(var(--un-hub-ring-width) + var(--un-hub-ring-offset-width)) var(--un-hub-ring-color);box-shadow:var(--un-hub-ring-offset-shadow), var(--un-hub-ring-shadow), var(--un-hub-shadow)}.ring-purple\\/50{--un-hub-ring-color:#c084fc80}.focus-within\\:ring-primary-500\\/40:focus-within{--un-hub-ring-color:rgb(from var(--colors-primary-500,#578861) r g b / .4)}.focus-within\\:ring-red-500\\/40:focus-within{--un-hub-ring-color:#ef444466}.focus\\:ring-primary-500\\/25:focus{--un-hub-ring-color:rgb(from var(--colors-primary-500,#578861) r g b / .25)}.focus\\:ring-primary-500\\/40:focus{--un-hub-ring-color:rgb(from var(--colors-primary-500,#578861) r g b / .4)}.focus\\:ring-primary\\/30:focus{--un-hub-ring-color:rgb(from var(--colors-primary-DEFAULT,#3a6a45) r g b / .3)}.focus\\:ring-red-500\\/40:focus{--un-hub-ring-color:#ef444466}.focus-visible\\:ring-primary-500\\/30:focus-visible{--un-hub-ring-color:rgb(from var(--colors-primary-500,#578861) r g b / .3)}.focus-visible\\:ring-primary-500\\/40:focus-visible{--un-hub-ring-color:rgb(from var(--colors-primary-500,#578861) r g b / .4)}.backdrop-blur-0{--un-hub-backdrop-blur:blur(0);backdrop-filter:var(--un-hub-backdrop-blur) var(--un-hub-backdrop-brightness) var(--un-hub-backdrop-contrast) var(--un-hub-backdrop-grayscale) var(--un-hub-backdrop-hue-rotate) var(--un-hub-backdrop-invert) var(--un-hub-backdrop-opacity) var(--un-hub-backdrop-saturate) var(--un-hub-backdrop-sepia)}.backdrop-blur-1{--un-hub-backdrop-blur:blur(1px);backdrop-filter:var(--un-hub-backdrop-blur) var(--un-hub-backdrop-brightness) var(--un-hub-backdrop-contrast) var(--un-hub-backdrop-grayscale) var(--un-hub-backdrop-hue-rotate) var(--un-hub-backdrop-invert) var(--un-hub-backdrop-opacity) var(--un-hub-backdrop-saturate) var(--un-hub-backdrop-sepia)}.backdrop-blur-sm{--un-hub-backdrop-blur:blur(4px);backdrop-filter:var(--un-hub-backdrop-blur) var(--un-hub-backdrop-brightness) var(--un-hub-backdrop-contrast) var(--un-hub-backdrop-grayscale) var(--un-hub-backdrop-hue-rotate) var(--un-hub-backdrop-invert) var(--un-hub-backdrop-opacity) var(--un-hub-backdrop-saturate) var(--un-hub-backdrop-sepia)}.blur-2xl{--un-hub-blur:blur(40px);filter:var(--un-hub-blur) var(--un-hub-brightness) var(--un-hub-contrast) var(--un-hub-drop-shadow) var(--un-hub-grayscale) var(--un-hub-hue-rotate) var(--un-hub-invert) var(--un-hub-saturate) var(--un-hub-sepia)}.invert{--un-hub-invert:invert(1);filter:var(--un-hub-blur) var(--un-hub-brightness) var(--un-hub-contrast) var(--un-hub-drop-shadow) var(--un-hub-grayscale) var(--un-hub-hue-rotate) var(--un-hub-invert) var(--un-hub-saturate) var(--un-hub-sepia)}.saturate-0,.disabled\\:saturate-0:disabled{--un-hub-saturate:saturate(0);filter:var(--un-hub-blur) var(--un-hub-brightness) var(--un-hub-contrast) var(--un-hub-drop-shadow) var(--un-hub-grayscale) var(--un-hub-hue-rotate) var(--un-hub-invert) var(--un-hub-saturate) var(--un-hub-sepia)}.filter{filter:var(--un-hub-blur) var(--un-hub-brightness) var(--un-hub-contrast) var(--un-hub-drop-shadow) var(--un-hub-grayscale) var(--un-hub-hue-rotate) var(--un-hub-invert) var(--un-hub-saturate) var(--un-hub-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-all{transition-property:all;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-opacity{transition-property:opacity;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-transform{transition-property:transform;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-150{transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-250{transition-duration:.25s}.duration-300{transition-duration:.3s}.delay-200{transition-delay:.2s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}@media (width>=640px){.sm\\:gap-2\\.5{gap:.625rem}.sm\\:p-6{padding:1.5rem}}*,:before,:after,::backdrop{--un-hub-rotate:0;--un-hub-rotate-x:0;--un-hub-rotate-y:0;--un-hub-rotate-z:0;--un-hub-scale-x:1;--un-hub-scale-y:1;--un-hub-scale-z:1;--un-hub-skew-x:0;--un-hub-skew-y:0;--un-hub-translate-x:0;--un-hub-translate-y:0;--un-hub-translate-z:0;--un-hub-pan-x: ;--un-hub-pan-y: ;--un-hub-pinch-zoom: ;--un-hub-scroll-snap-strictness:proximity;--un-hub-ordinal: ;--un-hub-slashed-zero: ;--un-hub-numeric-figure: ;--un-hub-numeric-spacing: ;--un-hub-numeric-fraction: ;--un-hub-border-spacing-x:0;--un-hub-border-spacing-y:0;--un-hub-ring-offset-shadow:0 0 #0000;--un-hub-ring-shadow:0 0 #0000;--un-hub-shadow-inset: ;--un-hub-shadow:0 0 #0000;--un-hub-ring-inset: ;--un-hub-ring-offset-width:0px;--un-hub-ring-offset-color:#fff;--un-hub-ring-width:0px;--un-hub-ring-color:#93c5fd80;--un-hub-blur: ;--un-hub-brightness: ;--un-hub-contrast: ;--un-hub-drop-shadow: ;--un-hub-grayscale: ;--un-hub-hue-rotate: ;--un-hub-invert: ;--un-hub-saturate: ;--un-hub-sepia: ;--un-hub-backdrop-blur: ;--un-hub-backdrop-brightness: ;--un-hub-backdrop-contrast: ;--un-hub-backdrop-grayscale: ;--un-hub-backdrop-hue-rotate: ;--un-hub-backdrop-invert: ;--un-hub-backdrop-opacity: ;--un-hub-backdrop-saturate: ;--un-hub-backdrop-sepia: }*,:before,:after{border-color:#8882}.border-base{--un-hub-border-opacity:.13;border-color:rgb(153 153 153/var(--un-hub-border-opacity))}.bg-active{--un-hub-bg-opacity:.19;background-color:rgb(153 153 153/var(--un-hub-bg-opacity))}.bg-base{--un-hub-bg-opacity:1;background-color:rgb(255 255 255/var(--un-hub-bg-opacity))}.dark .bg-base{--un-hub-bg-opacity:1;background-color:rgb(17 17 17/var(--un-hub-bg-opacity))}.bg-hover{--un-hub-bg-opacity:.13;background-color:rgb(153 153 153/var(--un-hub-bg-opacity))}.bg-secondary{--un-hub-bg-opacity:1;background-color:rgb(246 246 246/var(--un-hub-bg-opacity))}.dark .bg-secondary{--un-hub-bg-opacity:1;background-color:rgb(16 16 16/var(--un-hub-bg-opacity))}.color-active{--un-hub-text-opacity:1;color:rgb(from var(--colors-primary-600,#40704b) r g b / var(--un-hub-text-opacity))}.dark .color-active{--un-hub-text-opacity:1;color:rgb(from var(--colors-primary-300,#93c69d) r g b / var(--un-hub-text-opacity))}.color-base{--un-hub-text-opacity:1;color:rgb(38 38 38/var(--un-hub-text-opacity))}.dark .color-base{--un-hub-text-opacity:1;color:rgb(229 229 229/var(--un-hub-text-opacity))}.color-faint,.dark .color-faint{--un-hub-text-opacity:1;color:rgb(115 115 115/var(--un-hub-text-opacity))}.color-muted{--un-hub-text-opacity:1;color:rgb(82 82 82/var(--un-hub-text-opacity))}.dark .color-muted{--un-hub-text-opacity:1;color:rgb(163 163 163/var(--un-hub-text-opacity))}:root,:host,.devframes-accent-scope{--devframe-primary-DEFAULT:var(--devframe-primary);--devframe-primary-600:color-mix(in oklab, var(--devframe-primary), white 9%);--devframe-primary-500:color-mix(in oklab, var(--devframe-primary), white 18%);--devframe-primary-400:color-mix(in oklab, var(--devframe-primary), white 34%);--devframe-primary-300:color-mix(in oklab, var(--devframe-primary), white 54%);--colors-primary-DEFAULT:var(--devframe-primary-DEFAULT,#3a6a45);--colors-primary-600:var(--devframe-primary-600,#40704b);--colors-primary-500:var(--devframe-primary-500,#578861);--colors-primary-400:var(--devframe-primary-400,#75a67e);--colors-primary-300:var(--devframe-primary-300,#93c69d)}`;function lt(e){let t=e.ownerDocument?.defaultView;return t?.HTMLIFrameElement?e instanceof t.HTMLIFrameElement:typeof HTMLIFrameElement<`u`?e instanceof HTMLIFrameElement:`src`in e}function ut(e={}){let t=new Map,n=new Map,r=e.maxPanes??1/0,i,a,o=0,s,c=!1,l=0;function u(){let t=e.document??globalThis.document;if(!t)throw Error("[iframe-pane] No document available. Provide `options.document` or run in a DOM environment.");return t}function d(){return u().defaultView}function f(){if(e.container){let t=e.container,n=t.ownerDocument.defaultView?.getComputedStyle(t).position;return(!n||n===`static`)&&(t.style.position=`relative`),t}if(!i){let t=u();i=t.createElement(`div`),i.setAttribute(`data-iframe-panes`,``),Object.assign(i.style,{position:`fixed`,inset:`0`,pointerEvents:`none`}),e.zIndex!=null&&(i.style.zIndex=String(e.zIndex)),t.body.appendChild(i)}return i}function p(){return!a&&typeof ResizeObserver<`u`&&(a=new ResizeObserver(e=>{for(let t of e)n.get(t.target)?.forEach(e=>e.update())})),a}function m(){if(s!=null)return;let e=d();if(!e){h();return}s=e.requestAnimationFrame(()=>{s=void 0,h()})}function h(){for(let e of n.values())for(let t of e)t.update()}let g=()=>m();function _(){let e=d();e&&(n.size>0?(e.addEventListener(`scroll`,g,{capture:!0,passive:!0}),e.addEventListener(`resize`,g,{passive:!0})):(e.removeEventListener(`scroll`,g,{capture:!0}),e.removeEventListener(`resize`,g),s!=null&&(e.cancelAnimationFrame(s),s=void 0)))}function v(e,t){let r=n.get(t);r||(r=new Set,n.set(t,r),p()?.observe(t)),r.add(e),_()}function y(e,t){let r=n.get(t);r&&(r.delete(e),r.size===0&&(n.delete(t),a?.unobserve(t)),_())}function b(e){e.element.style.pointerEvents=o>0?`none`:e.stateStyle.pointerEvents}function x(e){if(t.size<=r)return;let n=[...t.values()].filter(t=>!t.isMounted&&t!==e).sort((e,t)=>e.lastActiveAt-t.lastActiveAt);for(let e of n){if(t.size<=r)break;e.dispose()}}class S{id;element;styleDefault;styleActive;styleHidden;stateKeys;target=null;isVisible=!1;isDisposed=!1;lastActiveAt=0;constructor(t,n){this.id=t,this.styleDefault={...e.styleDefault,...n.style},this.styleActive={opacity:`1`,pointerEvents:`auto`,...e.styleActive,...n.styleActive},this.styleHidden={opacity:`0.001`,pointerEvents:`none`,...e.styleHidden,...n.styleHidden},this.stateKeys=[...new Set([...Object.keys(this.styleActive),...Object.keys(this.styleHidden)])];let r=u(),i=this.element=n.element??r.createElement(n.tagName??`iframe`);if(i.setAttribute(`data-iframe-pane`,t),Object.assign(i.style,{display:`block`,position:`absolute`,left:`0px`,top:`0px`,border:`0`},this.styleDefault),n.attrs)for(let[e,t]of Object.entries(n.attrs))i.setAttribute(e,t);this.applyState(),n.onCreated?.(i),n.src!=null&<(i)&&(i.src=n.src),f().appendChild(i)}get iframe(){return lt(this.element)?this.element:void 0}get isMounted(){return this.target!=null}get stateStyle(){return this.isVisible?this.styleActive:this.styleHidden}applyState(){let e=this.stateStyle,t={};for(let n of this.stateKeys)n in e||(t[n]=this.styleDefault[n]??``);Object.assign(this.element.style,t,e),o>0&&(this.element.style.pointerEvents=`none`)}touch(){this.lastActiveAt=++l}mount(e){this.assertNotDisposed(),this.target!==e&&(this.target&&y(this,this.target),this.target=e,v(this,e)),this.show()}unmount(){this.target&&=(y(this,this.target),null),this.hide()}show(){this.assertNotDisposed(),this.touch(),this.isVisible=!0,this.applyState(),this.update()}hide(){this.isDisposed||(this.isVisible=!1,this.applyState())}update(){if(this.isDisposed||!this.target||!this.isVisible)return;let e=this.target.getBoundingClientRect(),t=f().getBoundingClientRect();Object.assign(this.element.style,{left:`${e.left-t.left}px`,top:`${e.top-t.top}px`,width:`${e.width}px`,height:`${e.height}px`})}dispose(){this.isDisposed||(this.unmount(),this.isDisposed=!0,this.element.remove(),t.delete(this.id),e.onPaneDisposed?.(this))}assertNotDisposed(){if(this.isDisposed)throw Error(`[iframe-pane] Pane "${this.id}" has been disposed`)}}return{list(){return[...t.values()]},get(e){return t.get(e)},has(e){return t.has(e)},ensure:((n,r={})=>{if(c)throw Error(`[iframe-pane] Manager has been disposed`);let i=t.get(n);return i?i.touch():(i=new S(n,r),i.touch(),t.set(n,i),e.onPaneCreated?.(i),x(i)),i}),get maxPanes(){return r},set maxPanes(e){r=e,x()},get isPointerLocked(){return o>0},lockPointerEvents(){o++,t.forEach(b);let e=!1;return()=>{e||(e=!0,o--,t.forEach(b))}},updateAll:h,get container(){return e.container??i},get isDisposed(){return c},dispose(){if(!c){c=!0;for(let e of[...t.values()])e.dispose();a?.disconnect(),a=void 0,_(),i?.remove(),i=void 0}}}}function dt(e){return e?e.type===`iframe`?e.frameId??e.id:e.id:null}function ft(e,t,n){let r=Ve(),i=Xe(e,e=>{e&&!r.value&&(r.value=Le(ut({container:e,document:e.ownerDocument})),i())},{immediate:!0,flush:`post`});if(n&&Xe(n,e=>{r.value?.list().forEach(t=>{t.id!==e&&t.hide()})},{flush:`post`}),t){Xe(()=>t,()=>r.value?.updateAll(),{deep:!0});let e;Xe(()=>t.isDragging||t.isResizing,t=>{t?e??=r.value?.lockPointerEvents():(e?.(),e=void 0)},{flush:`sync`}),Je(()=>e?.())}return Je(()=>r.value?.dispose()),r}function pt(e,t){let n=B(e.rpc.isTrusted),r=e.rpc.events.on(`rpc:is-trusted:updated`,e=>{n.value=e,t?.(e)});return Je(()=>r()),n}function mt(e){return Array.isArray?Array.isArray(e):Ct(e)===`[object Array]`}function ht(e){if(typeof e==`string`)return e;if(typeof e==`bigint`)return e.toString();let t=e+``;return t==`0`&&1/e==-1/0?`-0`:t}function gt(e){return e==null?``:ht(e)}function Y(e){return typeof e==`string`}function _t(e){return typeof e==`number`}function vt(e){return e===!0||e===!1||bt(e)&&Ct(e)==`[object Boolean]`}function yt(e){return typeof e==`object`}function bt(e){return yt(e)&&e!==null}function xt(e){return e!=null}function St(e){return!e.trim().length}function Ct(e){return e==null?e===void 0?`[object Undefined]`:`[object Null]`:Object.prototype.toString.call(e)}var wt=`Incorrect 'index' type`,Tt=`Invalid doc index: must be a non-negative integer within the bounds of the docs array`,Et=e=>`Invalid value for key ${e}`,Dt=e=>`Pattern length exceeds max of ${e}.`,Ot=e=>`Missing ${e} property in key`,kt=e=>`Property 'weight' in key '${e}' must be a positive integer`,At=`Fuse.match does not support useTokenSearch: token search requires corpus-level statistics (df, fieldCount) that a one-off string comparison does not have. Use new Fuse(...).search(...) instead.`,jt=Object.prototype.hasOwnProperty,Mt=class{constructor(e){this._keys=[],this._keyMap={};let t=0;e.forEach(e=>{let n=Nt(e);this._keys.push(n),this._keyMap[n.id]=n,t+=n.weight}),this._keys.forEach(e=>{e.weight/=t})}get(e){return this._keyMap[e]}keys(){return this._keys}toJSON(){return JSON.stringify(this._keys)}};function Nt(e){let t=null,n=null,r=null,i=1,a=null;if(Y(e)||mt(e))r=e,t=Pt(e),n=Ft(e);else{if(!jt.call(e,`name`))throw Error(Ot(`name`));let o=e.name;if(r=o,jt.call(e,`weight`)&&e.weight!==void 0&&(i=e.weight,i<=0))throw Error(kt(Ft(o)));t=Pt(o),n=Ft(o),a=e.getFn??null}return{path:t,id:n,weight:i,src:r,getFn:a}}function Pt(e){return mt(e)?e:e.split(`.`)}function Ft(e){return mt(e)?e.join(`.`):e}function It(e,t){let n=[],r=!1,i=(e,t,a,o)=>{if(xt(e)){if(!t[a])n.push(o===void 0?e:{v:e,i:o});else{let s=e[t[a]];if(!xt(s))return;if(a===t.length-1&&(Y(s)||_t(s)||vt(s)||typeof s==`bigint`))n.push(o===void 0?gt(s):{v:gt(s),i:o});else if(mt(s)){r=!0;for(let e=0,n=s.length;ee.score===t.score?e.idx=9&&e<=13||e===32||e===160}function Ht(e=1,t=3){let n=new Map,r=10**t;return{get(t){let i=0,a=!1;for(let e=0;e{this._keysMap[e.id]=t})}create(){if(this.isCreated||!this.docs.length)return;this.isCreated=!0;let e=this.docs.length;this.records=Array(e);let t=0;if(Y(this.docs[0]))for(let n=0;ne&&--this.records[t].i}removeAll(e){let t=new Set;for(let n of e)Number.isInteger(n)&&n>=0&&t.add(n);if(t.size===0)return;this.records=this.records.filter(e=>!t.has(e.i));let n=Array.from(t).sort((e,t)=>e-t);for(let e of this.records){let t=0,r=n.length;for(;t>>1;n[i]t),records:this.records}}};function Wt(e,t,{getFn:n=X.getFn,fieldNormWeight:r=X.fieldNormWeight}={}){let i=new Ut({getFn:n,fieldNormWeight:r});return i.setKeys(e.map(Nt)),i.setSources(t),i.create(),i}function Gt(e,{getFn:t=X.getFn,fieldNormWeight:n=X.fieldNormWeight}={}){let{keys:r,records:i}=e,a=new Ut({getFn:t,fieldNormWeight:n});return a.setKeys(r),a.setIndexRecords(i),a}function Kt(e=[],t=X.minMatchCharLength){let n=[],r=-1,i=-1,a=0;for(let o=e.length;a=t&&n.push([r,i]),r=-1)}return e[a-1]&&a-r>=t&&n.push([r,a-1]),n}function qt(e,t,n,{location:r=X.location,distance:i=X.distance,threshold:a=X.threshold,findAllMatches:o=X.findAllMatches,minMatchCharLength:s=X.minMatchCharLength,includeMatches:c=X.includeMatches,ignoreLocation:l=X.ignoreLocation}={}){if(t.length>32)throw Error(Dt(32));let u=t.length,d=e.length,f=Math.max(0,Math.min(r,d)),p=a,m=f,h=(e,t)=>{let n=e/u;if(l)return n;let r=Math.abs(f-t);return i?n+r/i:r?1:n},g=s>1||c,_=g?Array(d):[],v;for(;(v=e.indexOf(t,m))>-1;){let e=h(0,v);if(p=Math.min(e,p),m=v+u,g){let e=0;for(;e=a;--r){let i=r-1,o=n[e[i]];if(c[r]=(c[r+1]<<1|1)&o,t&&(c[r]|=(y[r+1]|y[r])<<1|1|y[r+1]),c[r]&C&&(b=h(t,i),b<=p)){if(p=b,m=i,x=t,m<=f)break;a=Math.max(1,2*f-m)}}if(h(t+1,f)>p)break;y=c}if(g&&m>=0){let t=Math.min(d-1,m+u-1+x);for(let r=m;r<=t;r+=1)n[e[r]]&&(_[r]=1)}let w={isMatch:m>=0,score:Math.max(.001,b)};if(g){let e=Kt(_,s);e.length?c&&(w.indices=e):w.isMatch=!1}return w}function Jt(e){let t={};for(let n=0,r=e.length;ne[0]-t[0]||e[1]-t[1]);let t=[e[0]];for(let n=1,r=e.length;ne.normalize(`NFD`).replace(/[\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u07FD\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08D3-\u08E1\u08E3-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962\u0963\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u09FE\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AFA-\u0AFF\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C00-\u0C04\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0D00-\u0D03\u0D3B\u0D3C\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0F18\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F\u109A-\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4-\u17D3\u17DD\u180B-\u180D\u1885\u1886\u18A9\u1920-\u192B\u1930-\u193B\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F\u1AB0-\u1ABE\u1B00-\u1B04\u1B34-\u1B44\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BE6-\u1BF3\u1C24-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF2-\u1CF4\u1CF7-\u1CF9\u1DC0-\u1DF9\u1DFB-\u1DFF\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA880\uA881\uA8B4-\uA8C5\uA8E0-\uA8F1\uA8FF\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9E5\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5\uAAF6\uABE3-\uABEA\uABEC\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F]/g,``).replace(Zt,e=>Xt[e]):e=>e,$t=class{constructor(e,{location:t=X.location,threshold:n=X.threshold,distance:r=X.distance,includeMatches:i=X.includeMatches,findAllMatches:a=X.findAllMatches,minMatchCharLength:o=X.minMatchCharLength,isCaseSensitive:s=X.isCaseSensitive,ignoreDiacritics:c=X.ignoreDiacritics,ignoreLocation:l=X.ignoreLocation}={}){if(this.options={location:t,threshold:n,distance:r,includeMatches:i,findAllMatches:a,minMatchCharLength:o,isCaseSensitive:s,ignoreDiacritics:c,ignoreLocation:l},e=s?e:e.toLowerCase(),e=c?Qt(e):e,this.pattern=e,this.chunks=[],!this.pattern.length)return;let u=(e,t)=>{this.chunks.push({pattern:e,alphabet:Jt(e),startIndex:t})},d=this.pattern.length;if(d>32){let e=0,t=d%32,n=d-t;for(;e{let{isMatch:m,score:h,indices:g}=qt(e,t,n,{location:i+p,distance:a,threshold:o,findAllMatches:s,minMatchCharLength:c,includeMatches:r,ignoreLocation:l});m&&(f=!0),d+=h,m&&g&&u.push(...g)});let p={isMatch:f,score:f?d/this.chunks.length:1};return f&&r&&(p.indices=Yt(u)),p}},en=new Set([`fuzzy`,`include`]);function tn(e){return e.startsWith(`inverse`)}var nn=[{type:`exact`,multiRegex:/^="(.*)"$/,singleRegex:/^=(.*)$/,create:e=>({type:`exact`,search(t){let n=t===e;return{isMatch:n,score:+!n,indices:[0,e.length-1]}}})},{type:`include`,multiRegex:/^'"(.*)"$/,singleRegex:/^'(.*)$/,create:e=>({type:`include`,search(t){let n=0,r,i=[],a=e.length;for(;(r=t.indexOf(e,n))>-1;)n=r+a,i.push([r,n-1]);let o=!!i.length;return{isMatch:o,score:+!o,indices:i}}})},{type:`prefix-exact`,multiRegex:/^\^"(.*)"$/,singleRegex:/^\^(.*)$/,create:e=>({type:`prefix-exact`,search(t){let n=t.startsWith(e);return{isMatch:n,score:+!n,indices:[0,e.length-1]}}})},{type:`inverse-prefix-exact`,multiRegex:/^!\^"(.*)"$/,singleRegex:/^!\^(.*)$/,create:e=>({type:`inverse-prefix-exact`,search(t){let n=!t.startsWith(e);return{isMatch:n,score:+!n,indices:[0,t.length-1]}}})},{type:`inverse-suffix-exact`,multiRegex:/^!"(.*)"\$$/,singleRegex:/^!(.*)\$$/,create:e=>({type:`inverse-suffix-exact`,search(t){let n=!t.endsWith(e);return{isMatch:n,score:+!n,indices:[0,t.length-1]}}})},{type:`suffix-exact`,multiRegex:/^"(.*)"\$$/,singleRegex:/^(.*)\$$/,create:e=>({type:`suffix-exact`,search(t){let n=t.endsWith(e);return{isMatch:n,score:+!n,indices:[t.length-e.length,t.length-1]}}})},{type:`inverse-exact`,multiRegex:/^!"(.*)"$/,singleRegex:/^!(.*)$/,create:e=>({type:`inverse-exact`,search(t){let n=t.indexOf(e)===-1;return{isMatch:n,score:+!n,indices:[0,t.length-1]}}})},{type:`fuzzy`,multiRegex:/^"(.*)"$/,singleRegex:/^(.*)$/,create:(e,t={})=>{let n=new $t(e,{location:t.location??X.location,threshold:t.threshold??X.threshold,distance:t.distance??X.distance,includeMatches:t.includeMatches??X.includeMatches,findAllMatches:t.findAllMatches??X.findAllMatches,minMatchCharLength:t.minMatchCharLength??X.minMatchCharLength,isCaseSensitive:t.isCaseSensitive??X.isCaseSensitive,ignoreDiacritics:t.ignoreDiacritics??X.ignoreDiacritics,ignoreLocation:t.ignoreLocation??X.ignoreLocation});return{type:`fuzzy`,search(e){return n.searchIn(e)}}}}],rn=nn.length,an=`\0`,on=`|`;function sn(e){let t=[],n=e.length,r=0;for(;r=n)break;let i=r;for(;i=n||e[t]===` `){i++;break}if(e[t]===`$`&&(t+1>=n||e[t+1]===` `)){i+=2;break}}i++}t.push(e.substring(r,i)),r=i}else{for(;i{let n=sn(e.replace(/\u0000/g,`|`).trim()).filter(e=>e&&!!e.trim()),r=[];for(let e=0,i=n.length;e!!(e[mn.AND]||e[mn.OR]),_n=e=>!!e[hn.PATH],vn=e=>!mt(e)&&yt(e)&&!gn(e),yn=e=>({[mn.AND]:Object.keys(e).map(t=>({[t]:e[t]}))});function bn(e,t,{auto:n=!0}={}){let r=e=>{if(Y(e)){let r={keyId:null,pattern:e};return n&&(r.searcher=pn(e,t)),r}let i=Object.keys(e),a=_n(e);if(!a&&i.length>1&&!gn(e))return r(yn(e));if(vn(e)){let r=a?e[hn.PATH]:i[0],o=a?e[hn.PATTERN]:e[r];if(!Y(o))throw Error(Et(r));let s={keyId:Ft(r),pattern:o};return n&&(s.searcher=pn(o,t)),s}let o={children:[],operator:i[0]};return i.forEach(t=>{let n=e[t];mt(n)&&n.forEach(e=>{o.children.push(r(e))})}),o};return gn(e)||(e=yn(e)),r(e)}function xn(e,{ignoreFieldNorm:t=X.ignoreFieldNorm}){let n=1;return e.forEach(({key:e,norm:r,score:i})=>{let a=e?e.weight:null;n*=(i===0&&a?2**-52:i)**+((a||1)*(t?1:r))}),n}function Sn(e,{ignoreFieldNorm:t=X.ignoreFieldNorm}){e.forEach(e=>{e.score=xn(e.matches,{ignoreFieldNorm:t})})}var Cn=class{constructor(e,t){this.limit=e,this.heap=[],this.comparator=t}get size(){return this.heap.length}insert(e){this.size0;){let n=e-1>>1;if(this.comparator(t[e],t[n])<=0)break;let r=t[e];t[e]=t[n],t[n]=r,e=n}}_sinkDown(e){let t=this.heap,n=t.length,r=e;do{e=r;let i=2*e+1,a=2*e+2;if(i0&&(r=i),a0&&(r=a),r!==e){let n=t[e];t[e]=t[r],t[r]=n}}while(r!==e)}};function wn(e){let t=[];return e.matches.forEach(e=>{if(!xt(e.indices)||!e.indices.length)return;let n={indices:e.indices,value:e.value};e.key&&(n.key=e.key.id),e.idx>-1&&(n.refIndex=e.idx),t.push(n)}),t}function Tn(e,t,{includeMatches:n=X.includeMatches,includeScore:r=X.includeScore}={}){return e.map(e=>{let{idx:i}=e,a={item:t[i],refIndex:i};return n&&(a.matches=wn(e)),r&&(a.score=e.score),a})}var En=/[\p{L}\p{M}\p{N}_]+/gu,Dn=new WeakSet;function On(e){Dn.has(e)||(Dn.add(e),console.warn(`[Fuse] tokenize regex ${e} lacks the global flag; only the first match per text will be returned. Add the 'g' flag.`))}function kn(e){if(typeof e==`function`){let t=!1;return n=>{let r=e(n);if(!t&&(t=!0,!Array.isArray(r)||r.some(e=>typeof e!=`string`)))throw Error(`[Fuse] tokenize function must return string[]; received ${Array.isArray(r)?`array containing non-strings`:typeof r}.`);return r}}return e instanceof RegExp?(e.global||On(e),t=>t.match(e)||[]):e=>e.match(En)||[]}function An({isCaseSensitive:e=!1,ignoreDiacritics:t=!1,tokenize:n}={}){let r=kn(n);return{tokenize(n){return e||(n=n.toLowerCase()),t&&(n=Qt(n)),r(n)}}}var jn=class{static condition(e,t){return t.useTokenSearch}constructor(e,t){this.options=t,this.analyzer=An({isCaseSensitive:t.isCaseSensitive,ignoreDiacritics:t.ignoreDiacritics,tokenize:t.tokenize});let n=this.analyzer.tokenize(e),{df:r,fieldCount:i}=t._invertedIndex;this.termSearchers=[],this.idfWeights=[];for(let e of n){this.termSearchers.push(new $t(e,{location:t.location,threshold:t.threshold,distance:t.distance,includeMatches:t.includeMatches,findAllMatches:t.findAllMatches,minMatchCharLength:t.minMatchCharLength,isCaseSensitive:t.isCaseSensitive,ignoreDiacritics:t.ignoreDiacritics,ignoreLocation:!0}));let n=r.get(e)||0,a=Math.log(1+(i-n+.5)/(n+.5));this.idfWeights.push(a)}this.combineAll=t.tokenMatch===`all`,this.numTerms=this.termSearchers.length,this.useMask=this.numTerms<=31}searchIn(e){if(!this.termSearchers.length)return{isMatch:!1,score:1};let t=[],n=0,r=0,i=0,a=0,o=this.combineAll&&!this.useMask?new Set:null;for(let s=0;s0?1-n/r:0,c={isMatch:!0,score:Math.max(.001,s)};return this.options.includeMatches&&t.length&&(c.indices=Yt(t)),this.combineAll&&(this.useMask?c.matchedMask=a:c.matchedTerms=o,c.termCount=this.numTerms),c}};function Mn(e,t,n,r){let i=r.tokenize(t);if(!i.length)return;e.fieldCount++,e.docFieldCount.set(n,(e.docFieldCount.get(n)||0)+1);let a=new Set(i),o=e.docTermFieldHits.get(n);o||(o=new Map,e.docTermFieldHits.set(n,o));for(let t of a)o.set(t,(o.get(t)||0)+1),e.df.set(t,(e.df.get(t)||0)+1)}function Nn(e,t,n,r){let{i,v:a,$:o}=t;if(a!==void 0){Mn(e,a,i,r);return}if(o)for(let t=0;te-t);for(let t of n)In(e,t);let r=e=>{let t=0,r=n.length;for(;t>>1;n[i]i?r(t):t,n);e.docFieldCount=a;let o=new Map;for(let[t,n]of e.docTermFieldHits)o.set(t>i?r(t):t,n);e.docTermFieldHits=o}var Rn=class{constructor(e,t,n){this.options={...X,...t},this.options.useExtendedSearch,this.options.useTokenSearch,this._keyStore=new Mt(this.options.keys),this._docs=e,this._myIndex=null,this._invertedIndex=null,this.setCollection(e,n),this._lastQuery=null,this._lastSearcher=null}_getSearcher(e){if(this._lastQuery===e)return this._lastSearcher;let t=pn(e,this._invertedIndex?{...this.options,_invertedIndex:this._invertedIndex}:this.options);return this._lastQuery=e,this._lastSearcher=t,t}setCollection(e,t){if(this._docs=e,t&&!(t instanceof Ut))throw Error(wt);if(this._myIndex=t||Wt(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight}),this.options.useTokenSearch){let e=An({isCaseSensitive:this.options.isCaseSensitive,ignoreDiacritics:this.options.ignoreDiacritics,tokenize:this.options.tokenize});this._invertedIndex=Pn(this._myIndex.records,this._myIndex.keys.length,e)}this._invalidateSearcherCache()}add(e){if(!xt(e))return;this._docs.push(e);let t=this._myIndex.add(e,this._docs.length-1);if(this._invertedIndex&&t){let e=An({isCaseSensitive:this.options.isCaseSensitive,ignoreDiacritics:this.options.ignoreDiacritics,tokenize:this.options.tokenize});Fn(this._invertedIndex,t,this._myIndex.keys.length,e)}this._invalidateSearcherCache()}remove(e=()=>!1){let t=[],n=[];for(let r=0,i=this._docs.length;r!e.has(n)),this._myIndex.removeAll(n),this._invalidateSearcherCache()}return t}removeAt(e){if(!Number.isInteger(e)||e<0||e>=this._docs.length)throw Error(Tt);this._invertedIndex&&Ln(this._invertedIndex,[e]);let t=this._docs.splice(e,1)[0];return this._myIndex.removeAt(e),this._invalidateSearcherCache(),t}_invalidateSearcherCache(){this._lastQuery=null,this._lastSearcher=null}getIndex(){return this._myIndex}_normalizedKeys(){return this._myIndex.keys.map(e=>this._keyStore.get(e.id)||e)}search(e,t){let{limit:n=-1}=t||{},{includeMatches:r,includeScore:i,shouldSort:a,sortFn:o,ignoreFieldNorm:s}=this.options;if(Y(e)&&!e.trim()){let e=this._docs.map((e,t)=>({item:e,refIndex:t}));return _t(n)&&n>-1&&(e=e.slice(0,n)),e}let c=a&&_t(n)&&n>0&&Y(e),l=o,u=(e,t)=>l(e,t)||e.idx-t.idx,d;if(c){let t=new Cn(n,u);Y(this._docs[0])?this._searchStringList(e,{heap:t,ignoreFieldNorm:s}):this._searchObjectList(e,{heap:t,ignoreFieldNorm:s}),d=t.extractSorted()}else d=Y(e)?Y(this._docs[0])?this._searchStringList(e):this._searchObjectList(e):this._searchLogical(e),Sn(d,{ignoreFieldNorm:s}),a&&d.sort(Y(e)?u:l),_t(n)&&n>-1&&(d=d.slice(0,n));return Tn(d,this._docs,{includeMatches:r,includeScore:i})}_searchStringList(e,{heap:t,ignoreFieldNorm:n}={}){let r=this._getSearcher(e),i=this.options.useTokenSearch&&this.options.tokenMatch===`all`,{records:a}=this._myIndex,o=t?null:[];return a.forEach(({v:e,i:a,n:s})=>{if(!xt(e))return;let c=r.searchIn(e);if(c.isMatch){let r={score:c.score,value:e,norm:s,indices:c.indices};i&&(r.matchedMask=c.matchedMask,r.matchedTerms=c.matchedTerms,r.termCount=c.termCount);let l=[r];if(!i||this._coversAllTokens(l)){let r={item:e,idx:a,matches:l};t?(r.score=xn(r.matches,{ignoreFieldNorm:n}),t.insert(r)):o.push(r)}}}),o}_searchLogical(e){let t=bn(e,this.options),n=this._normalizedKeys(),r=(e,t,i)=>{if(!(`children`in e)){let{keyId:r,searcher:a}=e,o;return r===null?(o=[],n.forEach((e,n)=>{o.push(...this._findMatches({key:e,value:t[n],searcher:a}))})):o=this._findMatches({key:this._keyStore.get(r),value:this._myIndex.getValueForItemAtKeyId(t,r),searcher:a}),o&&o.length?[{idx:i,item:t,matches:o}]:[]}let{children:a,operator:o}=e,s=[];for(let e=0,n=a.length;e{if(xt(e)){let i=r(t,e,n);i.length&&(a.has(n)||(a.set(n,{idx:n,item:e,matches:[]}),o.push(a.get(n))),i.forEach(({matches:e})=>{a.get(n).matches.push(...e)}))}}),o}_searchObjectList(e,{heap:t,ignoreFieldNorm:n}={}){let r=this._getSearcher(e),i=this.options.useTokenSearch&&this.options.tokenMatch===`all`,{records:a}=this._myIndex,o=this._normalizedKeys(),s=t?null:[];return a.forEach(({$:e,i:a})=>{if(!xt(e))return;let c=[],l=!1,u=!1;if(o.forEach((t,n)=>{let i=this._findMatches({key:t,value:e[n],searcher:r});i.length?(c.push(...i),i[0].hasInverse&&(u=!0)):l=!0}),!(u&&l)&&c.length&&(!i||this._coversAllTokens(c))){let r={idx:a,item:e,matches:c};t?(r.score=xn(r.matches,{ignoreFieldNorm:n}),t.insert(r)):s.push(r)}}),s}_findMatches({key:e,value:t,searcher:n}){if(!xt(t))return[];let r=[];if(mt(t))t.forEach(({v:t,i,n:a})=>{if(!xt(t))return;let o=n.searchIn(t);if(o.isMatch){let n={score:o.score,key:e,value:t,idx:i,norm:a,indices:o.indices,hasInverse:o.hasInverse};o.termCount!==void 0&&(n.matchedMask=o.matchedMask,n.matchedTerms=o.matchedTerms,n.termCount=o.termCount),r.push(n)}});else{let{v:i,n:a}=t,o=n.searchIn(i);if(o.isMatch){let t={score:o.score,key:e,value:i,norm:a,indices:o.indices,hasInverse:o.hasInverse};o.termCount!==void 0&&(t.matchedMask=o.matchedMask,t.matchedTerms=o.matchedTerms,t.termCount=o.termCount),r.push(t)}}return r}_coversAllTokens(e){let t=e.length?e[0].termCount:void 0;if(t===void 0)return!0;if(t<=31){let n=0;for(let t=0;tfn(e))};var zn=Rn,Bn={viewBox:`0 0 500 500`,fill:`none`,xmlns:`http://www.w3.org/2000/svg`,role:`img`,"aria-label":`Devframe logo`},Vn=[`fill`],Hn=J({__name:`DevframeLogo`,setup(e){let t=`devframe-logo-gradient-${ze()}`;return(e,n)=>(G(),j(`svg`,Bn,[P(`path`,{d:`M238.464 40H66V236.007H104.577C220.688 236.007 232.79 158.437 232.79 121.733C232.79 108.867 243.38 108.867 243.38 121.733C251.318 236.701 356.35 245.457 365.527 246.223L365.542 246.224C374.619 246.981 382.561 251.521 365.542 254.548C253.592 264.765 243.758 364.282 243.38 375.634C243.002 386.986 235.438 399.851 232.79 375.634C222.957 274.603 144.289 257.954 104.577 257.954H66V460.772H238.464C358.356 460.772 452.152 371.472 452.152 251.521C452.152 131.571 356.465 40 238.464 40Z`,fill:`url(#${t})`,stroke:`#86997F`,"stroke-opacity":`0.5`,"stroke-width":`1.33156`},null,8,Vn),P(`defs`,null,[P(`radialGradient`,{id:t,cx:`0`,cy:`0`,r:`1`,gradientTransform:`matrix(399.011 555.102 -568.423 408.982 -11.6221 -34.9218)`,gradientUnits:`userSpaceOnUse`},[...n[0]||=[P(`stop`,{"stop-color":`#ADC77F`},null,-1),P(`stop`,{offset:`0.71903`,"stop-color":`#517158`},null,-1),P(`stop`,{offset:`1`,"stop-color":`#486954`},null,-1)]])])]))}}),Un={class:`flex items-center gap-1.5 select-none`,"aria-hidden":`true`},Wn=[`src`,`alt`],Gn=[`src`,`alt`],Kn={class:`text-xl font-bold tracking-tight color-base`},qn=J({__name:`BrandWordmark`,setup(e){let t=qe(),n=tt(e=>e.wordmark),r=tt(e=>e.logo);return(e,i)=>(G(),j(`div`,Un,[U(n)?(G(),j(`img`,{key:0,src:U(n),alt:U(t).productName,class:`h-6 flex-none object-contain`,draggable:`false`},null,8,Wn)):(G(),j(H,{key:1},[U(r)?(G(),j(`img`,{key:0,src:U(r),alt:`${U(t).productName} logo`,class:`w-6 h-6 flex-none object-contain`,draggable:`false`},null,8,Gn)):(G(),F(Hn,{key:1,class:`w-6 h-6 flex-none`})),P(`span`,Kn,q(U(t).productName),1)],64))]))}}),Jn=typeof navigator<`u`&&/mac|iphone|ipad|ipod/i.test(navigator.platform||navigator.userAgent),Yn={mod:Jn?`meta`:`ctrl`,cmd:`meta`,command:`meta`,super:`meta`,win:`meta`,meta:`meta`,ctrl:`ctrl`,control:`ctrl`,alt:`alt`,option:`alt`,opt:`alt`,shift:`shift`},Xn={esc:`Escape`,escape:`Escape`,enter:`Enter`,return:`Enter`,space:` `,tab:`Tab`,up:`ArrowUp`,down:`ArrowDown`,left:`ArrowLeft`,right:`ArrowRight`},Zn=[`ctrl`,`alt`,`shift`,`meta`];function Qn(e){let t=e.trim().split(`+`).map(e=>e.trim()).filter(Boolean),n=new Set,r=``;for(let e of t){let t=e.toLowerCase(),i=Yn[t];i==null?r=Xn[t]??(e.length===1?e.toLowerCase():e):n.add(i)}return{modifiers:Zn.filter(e=>n.has(e)),key:r}}function $n(e){return e.trim().split(/\s+/).filter(Boolean).map(Qn)}var er={meta:`⌘`,ctrl:`⌃`,alt:`⌥`,shift:`⇧`},tr={meta:`Win`,ctrl:`Ctrl`,alt:`Alt`,shift:`Shift`},nr={Enter:`↵`,Escape:`Esc`,ArrowUp:`↑`,ArrowDown:`↓`,ArrowLeft:`←`,ArrowRight:`→`," ":`Space`};function rr(e){let t=e.modifiers.map(e=>(Jn?er[e]:tr[e])??e),n=nr[e.key]??(e.key.length===1?e.key.toUpperCase():e.key);return[...t,n]}function ir(e){return $n(e).flatMap(rr)}var ar={class:`inline-flex gap-0.5 items-center`},or={key:1,class:`text-micro color-muted leading-none font-mono px-1.5 border border-base rounded bg-sunken inline-flex h-5 min-w-5 items-center justify-center`},sr=J({__name:`DisplayKbd`,props:{keys:{}},setup(e){let t=e,n=N(()=>t.keys?ir(t.keys):[]);return(t,r)=>(G(),j(`span`,ar,[e.keys?(G(!0),j(H,{key:0},L(n.value,(e,t)=>(G(),j(`kbd`,{key:t,class:`text-micro color-muted leading-none font-mono px-1 border border-base rounded bg-sunken inline-flex h-5 min-w-5 items-center justify-center`},q(e),1))),128)):(G(),j(`kbd`,or,[Pe(t.$slots,`default`)]))]))}}),cr={class:`flex items-center justify-center`},lr=[`src`,`alt`],ur=J({__name:`BrandMark`,setup(e){let t=qe(),n=tt(e=>e.logo);return(e,r)=>(G(),j(`div`,cr,[U(n)?(G(),j(`img`,{key:0,src:U(n),alt:`${U(t).productName} logo`,class:`w-full h-full object-contain`,draggable:`false`},null,8,lr)):(G(),F(Hn,{key:1,class:`w-full h-full`}))]))}});function dr(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n`u`?null:Z(BigInt.prototype.toString),Ur=typeof Symbol>`u`?null:Z(Symbol.prototype.toString),Wr=Z(Object.prototype.hasOwnProperty),Gr=Z(Object.prototype.toString),Kr=Z(RegExp.prototype.test),qr=Jr(TypeError);function Z(e){return function(t){t instanceof RegExp&&(t.lastIndex=0);var n=[...arguments].slice(1);return Er(e,t,n)}}function Jr(e){return function(){return Dr(e,[...arguments])}}function Q(e,t){let n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:Pr;if(vr&&vr(e,null),!Nr(t))return e;let r=t.length;for(;r--;){let i=t[r];if(typeof i==`string`){let e=n(i);e!==i&&(yr(t)||(t[r]=e),i=e)}e[i]=!0}return e}function Yr(e){for(let t=0;t/g),pi=Cr(/\${[\w\W]*/g),mi=Cr(/^data-[\-\w.\u00B7-\uFFFF]+$/),hi=Cr(/^aria-[\-\w]+$/),gi=Cr(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),_i=Cr(/^(?:\w+script|data):/i),vi=Cr(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),yi=Cr(/^html$/i),bi=Cr(/^[a-z][.\w]*(-[.\w]+)+$/i),xi=Cr(/<[/\w!]/g),Si=Cr(/<[/\w]/g),Ci=Cr(/<\/no(script|embed|frames)/i),wi=Cr(/\/>/i),Ti={element:1,attribute:2,text:3,cdataSection:4,entityReference:5,entityNode:6,processingInstruction:7,comment:8,document:9,documentType:10,documentFragment:11,notation:12},Ei=[`style`,`script`,`xmp`,`iframe`,`noembed`,`noframes`,`plaintext`,`noscript`],Di=Sr(Q({},Ei)),Oi=function(){let e={};return Or(Ei,t=>{e[t]=Cr(RegExp(`])`,`i`))}),Sr(e)}(),ki=function(){return typeof window>`u`?null:window},Ai=function(e,t){if(typeof e!=`object`||typeof e.createPolicy!=`function`)return null;let n=null,r=`data-tt-policy-suffix`;t&&t.hasAttribute(r)&&(n=t.getAttribute(r));let i=`dompurify`+(n?`#`+n:``);try{return e.createPolicy(i,{createHTML(e){return e},createScriptURL(e){return e}})}catch{return console.warn(`TrustedTypes policy `+i+` could not be created.`),null}},ji=function(){return{afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}},Mi=function(e,t,n,r){return Wr(e,t)&&Nr(e[t])?Q(r.base?Xr(r.base):{},e[t],r.transform):n},Ni=function(e,t,n){let r=Wr(e,t)?e[t]:void 0;return r&&typeof r==`object`?Xr(r):n()};function Pi(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:ki(),t=e=>Pi(e);if(t.version=`3.4.14`,t.removed=[],!e||!e.document||e.document.nodeType!==Ti.document||!e.Element)return t.isSupported=!1,t;let n=e.document,r=n,i=r.currentScript;e.DocumentFragment;let a=e.HTMLTemplateElement,o=e.Node,s=e.Element,c=e.NodeFilter;e.NamedNodeMap===void 0&&(e.NamedNodeMap||e.MozNamedAttrMap),e.HTMLFormElement;let l=e.DOMParser,u=e.trustedTypes,d=s.prototype,f=Qr(d,`cloneNode`),p=Qr(d,`remove`),m=Qr(d,`nextSibling`),h=Qr(d,`childNodes`),g=Qr(d,`parentNode`),_=Qr(d,`shadowRoot`),v=Qr(d,`attributes`),y=o&&o.prototype?Qr(o.prototype,`nodeType`):null,b=o&&o.prototype?Qr(o.prototype,`nodeName`):null,x=o&&o.prototype?Qr(o.prototype,`ownerDocument`):null,S=function(e){return y?y(e):e.nodeType},C=function(e){return b?b(e):e.nodeName};if(typeof a==`function`){let e=n.createElement(`template`);e.content&&e.content.ownerDocument&&(n=e.content.ownerDocument)}let w,T=``,E,ee=!1,te=0,D=function(){if(te>0)throw qr(`A configured TRUSTED_TYPES_POLICY callback (createHTML or createScriptURL) must not call DOMPurify.sanitize, as that causes infinite recursion. Do not pass a policy whose callbacks wrap DOMPurify as TRUSTED_TYPES_POLICY; see the "DOMPurify and Trusted Types" section of the README.`)},ne=function(e){D(),te++;try{return w.createHTML(e)}finally{te--}},re=function(e){D(),te++;try{return w.createScriptURL(e)}finally{te--}},ie=function(){return ee||=(E=Ai(u,i),!0),E},ae=n,oe=ae.implementation,se=ae.createNodeIterator,ce=ae.createDocumentFragment,le=ae.getElementsByTagName,ue=r.importNode,O=ji();t.isSupported=typeof _r==`function`&&typeof g==`function`&&oe&&oe.createHTMLDocument!==void 0;let de=di,fe=fi,pe=pi,me=mi,he=hi,ge=_i,_e=vi,ve=bi,ye=gi,k=null,be=Q({},[...ei,...ti,...ni,...ii,...oi]),A=null,xe=Q({},[...si,...ci,...li,...ui]),Se=Object.seal(wr(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),j=null,Ce=null,we=Object.seal(wr(null,{tagCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeCheck:{writable:!0,configurable:!1,enumerable:!0,value:null}})),M=!0,N=!0,P=!1,Te=!0,Ee=!1,F=!0,De=!1,I=!1,Oe=null,ke=null,Ae=!1,je=!1,Me=!1,L=!1,Ne=!0,Pe=!1,Fe=`user-content-`,R=!0,z=!1,Ie={},Le=null,Re=Q({},`annotation-xml.audio.colgroup.desc.foreignobject.head.iframe.math.mi.mn.mo.ms.mtext.noembed.noframes.noscript.plaintext.script.selectedcontent.style.svg.template.thead.title.video.xmp`.split(`.`)),B=null,ze=Q({},[`audio`,`video`,`img`,`source`,`image`,`track`]),Be=null,Ve=Q({},[`alt`,`class`,`for`,`id`,`label`,`name`,`pattern`,`placeholder`,`role`,`summary`,`title`,`value`,`style`,`xmlns`]),He=`http://www.w3.org/1998/Math/MathML`,Ue=`http://www.w3.org/2000/svg`,We=`http://www.w3.org/1999/xhtml`,V=We,Ge=!1,Ke=null,H=Q({},[He,Ue,We],Fr),qe=Sr([`mi`,`mo`,`mn`,`ms`,`mtext`]),Je=Q({},qe),Ye=Sr([`annotation-xml`]),U=Q({},Ye),Xe=Q({},[`title`,`style`,`font`,`a`,`script`]),Ze=null,Qe=[`application/xhtml+xml`,`text/html`],W=null,G=null,$e=n.createElement(`form`),et=function(e){return e instanceof RegExp||e instanceof Function},K=function(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};if(G&&G===e)return;(!e||typeof e!=`object`)&&(e={}),e=Xr(e),Ze=Qe.indexOf(e.PARSER_MEDIA_TYPE)===-1?`text/html`:e.PARSER_MEDIA_TYPE,W=Ze===`application/xhtml+xml`?Fr:Pr,k=Mi(e,`ALLOWED_TAGS`,be,{transform:W}),A=Mi(e,`ALLOWED_ATTR`,xe,{transform:W}),Ke=Mi(e,`ALLOWED_NAMESPACES`,H,{transform:Fr}),Be=Mi(e,`ADD_URI_SAFE_ATTR`,Ve,{transform:W,base:Ve}),B=Mi(e,`ADD_DATA_URI_TAGS`,ze,{transform:W,base:ze}),Le=Mi(e,`FORBID_CONTENTS`,Re,{transform:W}),j=Mi(e,`FORBID_TAGS`,Xr({}),{transform:W}),Ce=Mi(e,`FORBID_ATTR`,Xr({}),{transform:W}),Ie=Wr(e,`USE_PROFILES`)?e.USE_PROFILES&&typeof e.USE_PROFILES==`object`?Xr(e.USE_PROFILES):e.USE_PROFILES:!1,M=e.ALLOW_ARIA_ATTR!==!1,N=e.ALLOW_DATA_ATTR!==!1,P=e.ALLOW_UNKNOWN_PROTOCOLS||!1,Te=e.ALLOW_SELF_CLOSE_IN_ATTR!==!1,Ee=e.SAFE_FOR_TEMPLATES||!1,F=e.SAFE_FOR_XML!==!1,De=e.WHOLE_DOCUMENT||!1,je=e.RETURN_DOM||!1,Me=e.RETURN_DOM_FRAGMENT||!1,L=e.RETURN_TRUSTED_TYPE||!1,Ae=e.FORCE_BODY||!1,Ne=e.SANITIZE_DOM!==!1,Pe=e.SANITIZE_NAMED_PROPS||!1,R=e.KEEP_CONTENT!==!1,z=e.IN_PLACE||!1,ye=$r(e.ALLOWED_URI_REGEXP)?e.ALLOWED_URI_REGEXP:gi,V=typeof e.NAMESPACE==`string`?e.NAMESPACE:We,Je=Ni(e,`MATHML_TEXT_INTEGRATION_POINTS`,()=>Q({},qe)),U=Ni(e,`HTML_INTEGRATION_POINTS`,()=>Q({},Ye));let t=Ni(e,`CUSTOM_ELEMENT_HANDLING`,()=>wr(null));if(Se=wr(null),Wr(t,`tagNameCheck`)&&et(t.tagNameCheck)&&(Se.tagNameCheck=t.tagNameCheck),Wr(t,`attributeNameCheck`)&&et(t.attributeNameCheck)&&(Se.attributeNameCheck=t.attributeNameCheck),Wr(t,`allowCustomizedBuiltInElements`)&&typeof t.allowCustomizedBuiltInElements==`boolean`&&(Se.allowCustomizedBuiltInElements=t.allowCustomizedBuiltInElements),Cr(Se),Ee&&(N=!1),Me&&(je=!0),Ie&&(k=Q({},oi),A=wr(null),Ie.html===!0&&(Q(k,ei),Q(A,si)),Ie.svg===!0&&(Q(k,ti),Q(A,ci),Q(A,ui)),Ie.svgFilters===!0&&(Q(k,ni),Q(A,ci),Q(A,ui)),Ie.mathMl===!0&&(Q(k,ii),Q(A,li),Q(A,ui))),we.tagCheck=null,we.attributeCheck=null,Wr(e,`ADD_TAGS`)&&(typeof e.ADD_TAGS==`function`?we.tagCheck=e.ADD_TAGS:Nr(e.ADD_TAGS)&&(k===be&&(k=Xr(k)),Q(k,e.ADD_TAGS,W))),Wr(e,`ADD_ATTR`)&&(typeof e.ADD_ATTR==`function`?we.attributeCheck=e.ADD_ATTR:Nr(e.ADD_ATTR)&&(A===xe&&(A=Xr(A)),Q(A,e.ADD_ATTR,W))),Wr(e,`ADD_FORBID_CONTENTS`)&&Nr(e.ADD_FORBID_CONTENTS)&&(Le===Re&&(Le=Xr(Le)),Q(Le,e.ADD_FORBID_CONTENTS,W)),R&&(k[`#text`]=!0),De&&Q(k,[`html`,`head`,`body`]),k.table&&(Q(k,[`tbody`]),delete j.tbody),e.TRUSTED_TYPES_POLICY){if(typeof e.TRUSTED_TYPES_POLICY.createHTML!=`function`)throw qr(`TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.`);if(typeof e.TRUSTED_TYPES_POLICY.createScriptURL!=`function`)throw qr(`TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.`);let t=w;w=e.TRUSTED_TYPES_POLICY;try{T=ne(``)}catch(e){throw w=t,e}}else e.TRUSTED_TYPES_POLICY===null?(w=void 0,T=``):(w===void 0&&(w=ie()),w&&typeof T==`string`&&(T=ne(``)));Sr&&Sr(e),G=e},tt=Q({},[...ti,...ni,...ri]),nt=Q({},[...ii,...ai]),rt=function(e,t,n){return t.namespaceURI===We?e===`svg`:t.namespaceURI===He?e===`svg`&&(n===`annotation-xml`||Je[n]):!!tt[e]},q=function(e,t,n){return t.namespaceURI===We?e===`math`:t.namespaceURI===Ue?e===`math`&&U[n]:!!nt[e]},it=function(e,t,n){return t.namespaceURI===Ue&&!U[n]||t.namespaceURI===He&&!Je[n]?!1:!nt[e]&&(Xe[e]||!tt[e])},at=function(e){let t=g(e);(!t||!t.tagName)&&(t={namespaceURI:V,tagName:`template`});let n=Pr(e.tagName),r=Pr(t.tagName);return Ke[e.namespaceURI]?e.namespaceURI===Ue?rt(n,t,r):e.namespaceURI===He?q(n,t,r):e.namespaceURI===We?it(n,t,r):!!(Ze===`application/xhtml+xml`&&Ke[e.namespaceURI]):!1},ot=function(e){jr(t.removed,{element:e});try{g(e).removeChild(e)}catch{if(p(e),!g(e))throw qr(`a node selected for removal could not be detached from its tree and cannot be safely returned; refusing to sanitize in place`)}},st=function(e,t,n){try{e.removeAttributeNode(t)}catch{try{e.removeAttribute(n)}catch{}}},J=function(e){ut(e);let t=h(e);if(t){let e=[];Or(t,t=>{jr(e,t)}),Or(e,e=>{try{p(e)}catch{}})}let n=v(e);if(n)for(let t=n.length-1;t>=0;--t){let r=n[t],i=r&&r.name;typeof i==`string`&&st(e,r,i)}},ct=function(e,n,r){if(!r)try{r=n.getAttributeNode(e)}catch{r=null}jr(t.removed,{attribute:r||null,from:n});try{r?n.removeAttributeNode(r):n.removeAttribute(e)}catch{try{n.removeAttribute(e)}catch{}}if(e===`is`){if(je||Me)try{ot(n)}catch{}else try{n.setAttribute(e,``)}catch{}}},lt=function(e){let t=v(e);if(t)for(let n=t.length-1;n>=0;--n){let r=t[n],i=r&&r.name;typeof i!=`string`||A[W(i)]||st(e,r,i)}},ut=function(e){let t=[e];for(;t.length>0;){let e=t.pop();S(e)===Ti.element&<(e);let n=h(e);if(n)for(let e=n.length-1;e>=0;--e)t.push(n[e])}},dt=function(e,t){return F?e===`patchsrc`||e===`for`&&t!==`label`&&t!==`output`:!1},ft=function(e){if(!F)return;let t=[e];for(;t.length>0;){let e=t.pop(),n=S(e);if(n===Ti.processingInstruction||n===Ti.comment&&Kr(Si,e.data)){try{p(e)}catch{}continue}if(n===Ti.element){let t=e,n=W(C(e));try{t.hasAttribute&&t.hasAttribute(`patchsrc`)&&t.removeAttribute(`patchsrc`),t.hasAttribute&&t.hasAttribute(`for`)&&dt(`for`,n)&&t.removeAttribute(`for`)}catch{}}let r=h(e);if(r)for(let e=r.length-1;e>=0;--e)t.push(r[e])}},pt=function(e){let t=null,r=null;if(Ae)e=``+e;else{let t=Ir(e,/^[\r\n\t ]+/);r=t&&t[0]}Ze===`application/xhtml+xml`&&V===We&&(e=``+e+``);let i=w?ne(e):e;if(V===We)try{t=new l().parseFromString(i,Ze)}catch{}if(!t||!t.documentElement){t=oe.createDocument(V,`template`,null);try{t.documentElement.innerHTML=Ge?T:i}catch{}}let a=t.body||t.documentElement;return e&&r&&a.insertBefore(n.createTextNode(r),a.childNodes[0]||null),V===We?le.call(t,De?`html`:`body`)[0]:De?t.documentElement:a},mt=function(e){let t=x?x(e):e.ownerDocument;return se.call(t||e,e,c.SHOW_ELEMENT|c.SHOW_COMMENT|c.SHOW_TEXT|c.SHOW_PROCESSING_INSTRUCTION|c.SHOW_CDATA_SECTION,null)},ht=function(e){return e=Lr(e,de,` `),e=Lr(e,fe,` `),e=Lr(e,pe,` `),e},gt=function(e){e.normalize();let t=x?x(e):e.ownerDocument,n=se.call(t||e,e,c.SHOW_TEXT|c.SHOW_COMMENT|c.SHOW_CDATA_SECTION|c.SHOW_PROCESSING_INSTRUCTION,null),r=n.nextNode();for(;r;)r.data=ht(r.data),r=n.nextNode();let i=e.querySelectorAll?.call(e,`template`);i&&Or(i,e=>{_t(e.content)&>(e.content)})},Y=function(e){let t=b?b(e):null;return typeof t!=`string`||W(t)!==`form`?!1:typeof e.nodeName!=`string`||typeof e.textContent!=`string`||typeof e.removeChild!=`function`||e.attributes!==v(e)||typeof e.removeAttribute!=`function`||typeof e.setAttribute!=`function`||typeof e.namespaceURI!=`string`||typeof e.insertBefore!=`function`||typeof e.hasChildNodes!=`function`||e.nodeType!==y(e)||e.childNodes!==h(e)},_t=function(e){if(!y||typeof e!=`object`||!e)return!1;try{return y(e)===Ti.documentFragment}catch{return!1}},vt=function(e){if(!y||typeof e!=`object`||!e)return!1;try{return typeof y(e)==`number`}catch{return!1}};function yt(e,n,r){e.length!==0&&Or(e,e=>{e.call(t,n,r,G)})}let bt=function(e,t){return!!(F&&e.hasChildNodes()&&!vt(e.firstElementChild)&&Kr(xi,e.textContent)&&Kr(xi,e.innerHTML)||F&&e.namespaceURI===We&&Di[t]&&(vt(e.firstElementChild)||typeof e.textContent==`string`&&Kr(Oi[t],e.textContent))||e.nodeType===Ti.processingInstruction||F&&e.nodeType===Ti.comment&&Kr(Si,e.data))},xt=function(e,t){return e instanceof RegExp?Kr(e,t):e instanceof Function&&!!e(t,...[...arguments].slice(2))},St=function(e,t,n){if(!j[t]&&Ot(t)&&xt(Se.tagNameCheck,t))return!1;if(R&&!Le[t]){let t=g(e),r=h(e);if(r&&t){let i=r.length;for(let a=i-1;a>=0;--a){let i=e===n?f(r[a],!0):r[a];t.insertBefore(i,m(e))}}}return ot(e),!0},Ct=function(e,t,n,r){return e.length===0?t:t===n||t===r?Xr(t):t},wt=function(e,t){return e===t||g(e)!==null?!1:(z&&ut(e),!0)},Tt=function(e,n){if(yt(O.beforeSanitizeElements,e,null),wt(e,n))return!0;if(Y(e))return ot(e),!0;let r=W(C(e));if(k=Ct(O.uponSanitizeElement,k,be,Oe),yt(O.uponSanitizeElement,e,{tagName:r,allowedTags:k}),wt(e,n))return!0;if(bt(e,r))return ot(e),!0;if(j[r]||!(we.tagCheck instanceof Function&&we.tagCheck(r))&&!k[r]){let t=St(e,r,n);return t===!1&&yt(O.afterSanitizeElements,e,null),t}if(S(e)===Ti.element&&!at(e)||(r===`noscript`||r===`noembed`||r===`noframes`)&&Kr(Ci,e.innerHTML))return ot(e),!0;if(Ee&&e.nodeType===Ti.text){let n=ht(e.textContent);e.textContent!==n&&(jr(t.removed,{element:e.cloneNode()}),e.textContent=n)}return yt(O.afterSanitizeElements,e,null),!1},Et=function(e,t,r){if(Ce[t]||dt(t,e)||Ne&&(t===`id`||t===`name`)&&(r in n||r in $e))return!1;let i=A[t]||we.attributeCheck instanceof Function&&we.attributeCheck(t,e);return N&&Kr(me,t)||M&&Kr(he,t)?!0:i?Be[t]||Kr(ye,Lr(r,_e,``))||(t===`src`||t===`xlink:href`||t===`href`)&&e!==`script`&&Rr(r,`data:`)===0&&B[e]||P&&!Kr(ge,Lr(r,_e,``))?!0:!r:Ot(e)&&xt(Se.tagNameCheck,e)&&xt(Se.attributeNameCheck,t,e)||t===`is`&&Se.allowCustomizedBuiltInElements&&xt(Se.tagNameCheck,r)},Dt=Q({},[`annotation-xml`,`color-profile`,`font-face`,`font-face-format`,`font-face-name`,`font-face-src`,`font-face-uri`,`missing-glyph`]),Ot=function(e){return!Dt[Pr(e)]&&Kr(ve,e)},kt=function(e,t,n,r){if(w&&typeof u==`object`&&typeof u.getAttributeType==`function`&&!n)switch(u.getAttributeType(e,t)){case`TrustedHTML`:return ne(r);case`TrustedScriptURL`:return re(r)}return r},At=function(e,n,r,i){try{r?e.setAttributeNS(r,n,i):e.setAttribute(n,i),Y(e)?ot(e):Ar(t.removed)}catch{ct(n,e)}},jt=function(e){yt(O.beforeSanitizeAttributes,e,null);let t=e.attributes;if(!t||Y(e))return;A=Ct(O.uponSanitizeAttribute,A,xe,ke);let n={attrName:``,attrValue:``,keepAttr:!0,allowedAttributes:A,forceKeepAttr:void 0},r=t.length,i=W(e.nodeName);for(;r--;){let a=t[r],o=a.name,s=a.namespaceURI,c=a.value,l=W(o),u=c,d=o===`value`?u:zr(u);if(n.attrName=l,n.attrValue=d,n.keepAttr=!0,n.forceKeepAttr=void 0,yt(O.uponSanitizeAttribute,e,n),d=n.attrValue,Pe&&(l===`id`||l===`name`)&&Rr(d,Fe)!==0&&(ct(o,e,a),d=Fe+d),F&&Kr(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i,d)){ct(o,e,a);continue}if(l===`attributename`&&Ir(d,`href`)){ct(o,e,a);continue}if(!n.forceKeepAttr){if(!n.keepAttr){ct(o,e,a);continue}if(!Te&&Kr(wi,d)){ct(o,e,a);continue}if(Ee&&(d=ht(d)),!Et(i,l,d)){ct(o,e,a);continue}d=kt(i,l,s,d),d!==u&&At(e,o,s,d)}}yt(O.afterSanitizeAttributes,e,null)},Mt=function(e){let t=null,n=mt(e);for(yt(O.beforeSanitizeShadowDOM,e,null);t=n.nextNode();)if(yt(O.uponSanitizeShadowNode,t,null),Tt(t,e),jt(t),_t(t.content)&&Mt(t.content),S(t)===Ti.element){let e=_(t);_t(e)&&(Nt(e),Mt(e))}yt(O.afterSanitizeShadowDOM,e,null)},Nt=function(e){let t=[{node:e,shadow:null}];for(;t.length>0;){let e=t.pop();if(e.shadow){Mt(e.shadow);continue}let n=e.node,r=S(n)===Ti.element,i=h(n);if(i)for(let e=i.length-1;e>=0;--e)t.push({node:i[e],shadow:null});if(r){let e=b?b(n):null;if(typeof e==`string`&&W(e)===`template`){let e=n.content;_t(e)&&t.push({node:e,shadow:null})}}if(r){let e=_(n);_t(e)&&t.push({node:null,shadow:e},{node:e,shadow:null})}}};return t.sanitize=function(e){let n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},i=null,a=null,o=null,s=null;if(Ge=!e,Ge&&(e=``),typeof e!=`string`&&!vt(e)&&(e=Zr(e),typeof e!=`string`))throw qr(`dirty is not a string, aborting`);if(!t.isSupported)return e;I?(k=Oe,A=ke):K(n),(O.uponSanitizeElement.length>0||O.uponSanitizeAttribute.length>0)&&(k=Xr(k)),O.uponSanitizeAttribute.length>0&&(A=Xr(A)),t.removed=[];let c=z&&typeof e!=`string`&&vt(e);if(c){ft(e);let t=C(e);if(typeof t==`string`){let n=W(t);if(!k[n]||j[n])throw J(e),qr(`root node is forbidden and cannot be sanitized in-place`)}if(Y(e))throw J(e),qr(`root node is clobbered and cannot be sanitized in-place`);try{Nt(e)}catch(t){throw J(e),t}}else if(vt(e))i=pt(``),a=i.ownerDocument.importNode(e,!0),a.nodeType===Ti.element&&a.nodeName===`BODY`||a.nodeName===`HTML`?i=a:i.appendChild(a),Nt(a);else{if(!je&&!Ee&&!De&&e.indexOf(`<`)===-1)return w&&L?ne(e):e;if(i=pt(e),!i)return je?null:L?T:``}i&&Ae&&ot(i.firstChild);let l=c?e:i;try{let e=mt(l);for(;o=e.nextNode();)Tt(o,l),jt(o),_t(o.content)&&Mt(o.content)}catch(n){throw c&&(J(e),Or(t.removed,e=>{e.element&&ut(e.element)})),n}if(c)return Or(t.removed,e=>{e.element&&ut(e.element)}),Ee&>(e),e;if(je){if(Ee&>(i),Me)for(s=ce.call(i.ownerDocument);i.firstChild;)s.appendChild(i.firstChild);else s=i;return(A.shadowroot||A.shadowrootmode)&&(s=ue.call(r,s,!0)),s}let u=De?i.outerHTML:i.innerHTML;return De&&k[`!doctype`]&&i.ownerDocument&&i.ownerDocument.doctype&&i.ownerDocument.doctype.name&&Kr(yi,i.ownerDocument.doctype.name)&&(u=` --`+u),Ee&&(u=ht(u)),w&&L?ne(u):u},t.setConfig=function(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};K(e),I=!0,Oe=k,ke=A},t.clearConfig=function(){G=null,I=!1,Oe=null,ke=null,w=E,T=``},t.isValidAttribute=function(e,t,n){G||K({});let r=W(e),i=W(t);return Et(r,i,n)},t.addHook=function(e,t){typeof t==`function`&&Wr(O,e)&&jr(O[e],t)},t.removeHook=function(e,t){if(Wr(O,e)){if(t!==void 0){let n=kr(O[e],t);return n===-1?void 0:Mr(O[e],n,1)[0]}return Ar(O[e])}},t.removeHooks=function(e){Wr(O,e)&&(O[e]=[])},t.removeAllHooks=function(){O=ji()},t}var Fi=Pi(),Ii=new Map,Li=Fi();async function Ri(e,t){let n=`${e}:${t}`;if(Ii.has(n))return Ii.get(n);let r=i().then(e=>(Ii.set(n,e),e)).catch(e=>{throw Ii.delete(n),e});return Ii.set(n,r),r;async function i(){let n=`https://api.iconify.design/${e}/${t}.svg?color=currentColor&width=100%`,r=await fetch(n,{signal:AbortSignal.timeout(1e4)}).then(e=>e.text());return Li.sanitize(r)}}var zi=[`innerHTML`],Bi=[`src`],Vi=J({__name:`IconifyIcon`,props:{icon:{}},setup(e){let t=e,n=N(()=>t.icon.includes(`/`)||t.icon.startsWith(`data:`)||t.icon.startsWith(`builtin:`)),r=N(()=>{if(n.value)return;let e=t.icon.match(/^(?:i-)?([\w-]+):([\w-]+)$/);if(e)return{collection:e[1],icon:e[2]}}),i=B(void 0);return $e(async()=>{if(!r.value){i.value=void 0;return}try{i.value=await Ri(r.value.collection,r.value.icon)}catch{i.value=void 0}}),(t,n)=>r.value?(G(),j(`div`,{key:0,innerHTML:i.value},null,8,zi)):(G(),j(`img`,{key:1,src:e.icon,class:`w-full h-full m-auto`,draggable:`false`},null,8,Bi))}}),Hi={key:1,class:`flex items-center justify-center`},Ui=J({__name:`DockIcon`,props:{icon:{},title:{}},setup(e){let t=e,n=N(()=>typeof t.icon==`string`?{dark:t.icon,light:t.icon}:t.icon);return(t,r)=>n.value.light===`builtin:devframes`?(G(),F(ur,{key:0})):(G(),j(`div`,Hi,[n.value.light===n.value.dark?(G(),F(Vi,{key:0,icon:n.value.light,title:e.title},null,8,[`icon`,`title`])):(G(),j(H,{key:1},[I(Vi,{class:`dark-hidden`,icon:n.value.light,title:e.title},null,8,[`icon`,`title`]),I(Vi,{class:`light-hidden`,icon:n.value.dark,title:e.title},null,8,[`icon`,`title`])],64))]))}}),Wi=[`id`],Gi={class:`flex items-center gap-2 flex-1 of-hidden min-w-0`},Ki={class:`flex-1 min-w-0`},qi={class:`flex items-center gap-1.5`},Ji={class:`truncate`},Yi={key:0,class:`op50`},Xi={key:0,class:`text-[10px] px-1 py-0 rounded bg-blue/10 text-blue shrink-0 leading-4`},Zi={key:0,class:`truncate text-xs op40 mt-0.5`},Qi={class:`flex items-center gap-1.5 flex-none`},$i={key:0,class:`i-ph-spinner-gap-duotone w-3.5 h-3.5 animate-spin op50`},ea={key:1,class:`i-ph-caret-right w-3 h-3 op40`},ta=J({__name:`CommandPaletteItem`,props:{entry:{},parentTitle:{},showParentTitle:{type:Boolean},selected:{type:Boolean},loading:{type:Boolean},keybindings:{}},emits:[`select`,`activate`],setup(e){return(t,n)=>(G(),j(`button`,{id:`cmd-${e.entry.id}`,class:`w-full text-left`,onClick:n[0]||=e=>t.$emit(`activate`),onMouseover:n[1]||=e=>t.$emit(`select`)},[P(`div`,{class:V([`flex items-center gap-2 justify-between rounded-md px-2.5 py-1.5 text-sm transition-colors`,e.selected?`bg-primary/10 text-primary`:`op80 hover:op100`])},[P(`div`,Gi,[e.entry.icon?(G(),F(Ui,{key:0,icon:e.entry.icon,class:`w-4 h-4 flex-none op70`},null,8,[`icon`])):A(``,!0),P(`div`,Ki,[P(`div`,qi,[P(`span`,Ji,[e.parentTitle&&e.showParentTitle?(G(),j(`span`,Yi,q(e.parentTitle)+` › `,1)):A(``,!0),M(` `+q(e.entry.title),1)]),e.entry.source===`server`?(G(),j(`span`,Xi,`server`)):A(``,!0)]),e.selected&&e.entry.description?(G(),j(`div`,Zi,q(e.entry.description),1)):A(``,!0)])]),P(`div`,Qi,[(G(!0),j(H,null,L(e.keybindings,(e,t)=>(G(),F(sr,{key:t,keys:e.key},null,8,[`keys`]))),128)),e.loading?(G(),j(`span`,$i)):e.entry.children?.length?(G(),j(`span`,ea)):A(``,!0)])],2)],40,Wi))}}),na={class:`absolute inset-0 flex items-start justify-center relative pt-[20vh] pointer-events-none`},ra={class:`w-full w-lg bg-glass:80 color-base border border-base rounded-lg shadow-xl pointer-events-auto of-hidden flex flex-col max-h-[60vh]`},ia={class:`border-b border-base flex items-center px-3`},aa=[`onClick`],oa={key:0,class:`py-8 flex flex-col items-center justify-center gap-2 op50 text-sm`},sa={key:0},ca={class:`text-primary op100`},la={key:1},ua={class:`border-t border-base flex items-center justify-between gap-4 px-3 py-1.5 text-[10px] op50`},da={class:`flex items-center gap-1.5`},fa=J({__name:`CommandPalette`,props:{context:{}},setup(e){let t=e,n=N(()=>t.context.commands),i=N({get:()=>n.value.paletteOpen,set:e=>{n.value.paletteOpen=e}}),a=B(``),o=B(0),s=K(`searchInput`),c=K(`listContainer`),l=B(!1),u=B([]),d=N(()=>x(n.value.paletteCommands)),f=N(()=>u.value.length>0?u.value.at(-1).items.map(e=>({entry:e,searchTitle:e.title})):d.value),p=B(),m=N(()=>p.value?p.value.map(e=>({entry:e,searchTitle:e.title})):f.value),_=N(()=>new zn(m.value,{keys:[`searchTitle`,`entry.description`,`entry.id`],distance:50,threshold:.4})),v=N(()=>a.value?_.value.search(a.value).map(e=>e.item):m.value);Xe(a,()=>{o.value=0});function y(e){a.value=``,o.value=0,p.value=void 0;let t=h(n.value.paletteCommands,e);u.value=t,e!=null&&!t.some(t=>t.id===e)&&(n.value.paletteScopeId=null)}Xe(i,e=>{e?(y(n.value.paletteScopeId),requestAnimationFrame(()=>{l.value=!0}),Ae(()=>s.value?.focus())):(l.value=!1,n.value.paletteScopeId=null)}),Xe(()=>n.value.paletteScopeId,e=>{i.value&&y(e)}),Xe(()=>n.value.paletteCommands,e=>{if(!i.value||u.value.length===0)return;let t=n.value.paletteScopeId,r=t!=null&&u.value.some(e=>e.id===t),a=fe(e,u.value,t);u.value=a,o.value=Math.min(o.value,Math.max(v.value.length-1,0)),r&&t!=null&&!a.some(e=>e.id===t)&&(n.value.paletteScopeId=null)});function b(e){let t=v.value.length;t!==0&&(o.value=(o.value+e+t)%t,S())}function S(){let e=v.value[o.value];e&&((c.value?.getRootNode())?.getElementById(`cmd-${e.entry.id}`))?.scrollIntoView({block:`nearest`})}let C=B(null);async function w(e){let r=m.value.find(t=>t.entry.id===e.entry.id)?.entry;if(r){if(g(r,t.context.docks.entries)===`drill`){n.value.paletteScopeId=r.id;return}if(r.source===`client`&&r.action){try{let e=await r.action();if(Array.isArray(e)){p.value=e,a.value=``,o.value=0;return}}catch(e){console.error(`[@devframes/hub-ui] Command "${r.id}" failed:`,e)}if(pe(r,t.context.docks.entries,n.value.paletteOpen,n.value.paletteScopeId))return;T();return}if(r.source===`server`){C.value=r.id;try{await n.value.execute(r.id)}catch(e){console.error(`[@devframes/hub-ui] Command "${r.id}" failed:`,e)}finally{C.value=null}T();return}T()}}function T(){l.value=!1,setTimeout(()=>{i.value=!1,a.value=``,u.value=[],p.value=void 0},150)}function E(){if(p.value){p.value=void 0,a.value=``,o.value=0;return}if(u.value.length>0){u.value.pop(),n.value.paletteScopeId=r(u.value),a.value=``,o.value=0;return}T()}function ee(e){u.value.splice(e),n.value.paletteScopeId=r(u.value),a.value=``,o.value=0}function te(e){e.key===`Backspace`&&!a.value&&(u.value.length>0||p.value)&&(e.preventDefault(),E())}function D(e){if(i.value)switch(e.key){case`ArrowDown`:case`ArrowUp`:e.preventDefault(),b(e.key===`ArrowDown`?1:-1);break;case`Enter`:{let t=v.value[o.value];t&&(e.preventDefault(),w(t));break}case`Escape`:e.preventDefault(),u.value.length>0||p.value?E():T()}}function ne(e){return n.value.getKeybindings(e)}return(e,t)=>i.value?(G(),j(`div`,{key:0,class:`devframes-command-palette fixed inset-0 z-command-palette`,onKeydown:D},[P(`div`,{class:V([`absolute inset-0 bg-white/50 dark:bg-black/30 transition-opacity duration-150`,l.value?`opacity-100 backdrop-blur-1`:`opacity-0 backdrop-blur-0`]),onClick:T},null,2),P(`div`,na,[P(`div`,{class:V([`flex flex-col transition-all duration-150`,l.value?`opacity-100 scale-100 translate-y-0`:`opacity-0 scale-98 -translate-y-2`])},[I(qn,{class:`absolute top--32px left-5px pointer-events-none`}),P(`div`,ra,[P(`header`,ia,[u.value.length>0?(G(!0),j(H,{key:0},L(u.value,(e,n)=>(G(),j(`button`,{key:n,class:`text-xs op60 hover:op80 mr-1 flex items-center gap-0.5`,onClick:e=>ee(n)},[M(q(e.title)+` `,1),t[1]||=P(`span`,{class:`op40`},`›`,-1)],8,aa))),128)):A(``,!0),Ze(P(`input`,{ref_key:`searchInput`,ref:s,"onUpdate:modelValue":t[0]||=e=>a.value=e,class:`flex-1 bg-transparent py-3 outline-none text-sm color-base`,placeholder:`Type a command...`,onKeydown:te},null,544),[[ke,a.value]])]),P(`div`,{ref_key:`listContainer`,ref:c,class:`flex-1 of-y-auto p-1.5`},[(G(!0),j(H,null,L(v.value,(e,t)=>(G(),F(ta,{key:e.entry.id,entry:e.entry,"parent-title":e.parentTitle,"show-parent-title":!u.value.length,selected:o.value===t,loading:C.value===e.entry.id,keybindings:ne(e.entry.id),onSelect:e=>o.value=t,onActivate:t=>w(e)},null,8,[`entry`,`parent-title`,`show-parent-title`,`selected`,`loading`,`keybindings`,`onSelect`,`onActivate`]))),128)),v.value.length?A(``,!0):(G(),j(`div`,oa,[t[4]||=P(`div`,{class:`i-ph-magnifying-glass-duotone w-6 h-6`},null,-1),a.value?(G(),j(`div`,sa,[t[2]||=M(` No results for "`,-1),P(`strong`,ca,q(a.value),1),t[3]||=M(`" `,-1)])):(G(),j(`div`,la,` No commands available `))]))],512),P(`footer`,ua,[t[6]||=P(`div`,{class:`flex items-center gap-1.5`},[P(`kbd`,{class:`px-1 py-0.5 rounded border border-base bg-base font-mono`},`↓↑`),P(`span`,null,`navigate`)],-1),P(`div`,da,[t[5]||=P(`kbd`,{class:`px-1 py-0.5 rounded border border-base bg-base font-mono`},`esc`,-1),P(`span`,null,q(u.value.length>0||p.value?`back`:`close`),1)]),t[7]||=P(`div`,{class:`flex items-center gap-1.5`},[P(`kbd`,{class:`px-1 py-0.5 rounded border border-base bg-base font-mono`},`↵`),P(`span`,null,`select`)],-1)])])],2)])],32)):A(``,!0)}}),pa=J({__name:`FeedbackSpinner`,props:{size:{default:`1em`}},setup(e){let t=e,n=N(()=>typeof t.size==`number`?`${t.size}px`:t.size);return(e,t)=>(G(),j(`span`,{class:`border-2 border-current border-t-transparent rounded-full inline-block animate-spin`,style:R({width:n.value,height:n.value}),role:`status`,"aria-label":`Loading`},null,4))}}),ma=J({__name:`ActionButton`,props:{as:{},to:{},href:{},icon:{},variant:{default:`action`},size:{default:`md`},loading:{type:Boolean},disabled:{type:Boolean}},setup(e){let t=e,n=N(()=>t.as?t.as:t.href!=null||t.to!=null?`a`:`button`),r=N(()=>n.value===`a`),i=N(()=>n.value===`button`),a=N(()=>t.size===`sm`),o=N(()=>t.variant===`primary`?a.value?`btn-primary text-sm`:`btn-primary`:t.variant===`text`?a.value?`btn-text text-sm`:`btn-text`:a.value?`btn-action-sm`:`btn-action`),s=N(()=>t.disabled||t.loading);return(t,a)=>(G(),F(he(n.value),{class:V([o.value,{"pointer-events-none op-mute":s.value&&!i.value}]),href:r.value?e.href??e.to:void 0,to:e.as&&e.to!=null?e.to:void 0,disabled:i.value?s.value:void 0,"aria-disabled":s.value||void 0,"aria-busy":e.loading||void 0},{default:z(()=>[e.loading?(G(),F(pa,{key:0,size:`1em`})):e.icon?(G(),j(`span`,{key:1,class:V(e.icon),"aria-hidden":`true`},null,2)):A(``,!0),Pe(t.$slots,`default`)]),_:3},8,[`class`,`href`,`to`,`disabled`,`aria-disabled`,`aria-busy`]))}}),ha=Ye({singleton:!0});function ga(){return ha.start}var _a=[`aria-labelledby`,`onKeydown`],va=[`onClick`],ya={class:`relative w-full max-w-96 bg-base border border-base rounded-lg shadow-xl p-5`},ba={key:0,id:`devframes-confirm-title`,class:`text-sm font-medium leading-5`},xa={class:`flex items-center justify-end gap-2 mt-6`},Sa=J({__name:`Confirm`,setup(e){let t=K(`confirmButton`);Ge(()=>{(t.value?.$el)?.focus({preventScroll:!0})});function n(e,t){e(t)}return(e,r)=>(G(),F(U(ha),null,{default:z(({resolve:e,args:[r]})=>[P(`div`,{class:`devframes-confirm fixed inset-0 z-dock-confirm flex items-center justify-center p-4 color-base`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":r.title?`devframes-confirm-title`:void 0,"aria-describedby":`devframes-confirm-message`,onKeydown:ye(Te(t=>n(e,!1),[`prevent`,`stop`]),[`esc`])},[P(`div`,{class:`absolute inset-0 bg-black/30 dark:bg-black/45 backdrop-blur-1 cursor-default`,"aria-hidden":`true`,onClick:t=>n(e,!1)},null,8,va),P(`div`,ya,[r.title?(G(),j(`h3`,ba,q(r.title),1)):A(``,!0),P(`p`,{id:`devframes-confirm-message`,class:V([`text-xs op60 leading-5`,r.title?`mt-1.5`:``])},q(r.message),3),P(`div`,xa,[I(ma,{variant:`text`,size:`sm`,onClick:t=>n(e,!1)},{default:z(()=>[M(q(r.cancelText??`Cancel`),1)]),_:2},1032,[`onClick`]),I(ma,{ref_key:`confirmButton`,ref:t,variant:`primary`,size:`sm`,onClick:t=>n(e,!0)},{default:z(()=>[M(q(r.confirmText??`OK`),1)]),_:2},1032,[`onClick`])])])],40,_a)]),_:1}))}}),Ca=nt([]),wa=new Map;function Ta(){return Ca}function Ea(e){let t=Ca.find(t=>t.id===e.id);if(t){t.entry=e;let n=wa.get(e.id);n&&(clearTimeout(n),wa.delete(e.id));let r=e.autoDismiss??5e3;r!==!1&&wa.set(e.id,setTimeout(Da,r,e.id));return}let n={id:e.id,entry:e};Ca.push(n);let r=e.autoDismiss??5e3;r!==!1&&wa.set(e.id,setTimeout(Da,r,e.id))}function Da(e){let t=Ca.findIndex(t=>t.id===e);t!==-1&&Ca.splice(t,1);let n=wa.get(e);n&&(clearTimeout(n),wa.delete(e))}function Oa(e,t,n){return e.notify?t?JSON.stringify(e)!==JSON.stringify(t):!n||e.status===`loading`:!1}var ka;function Aa(e){if(ka)return ka;let t=ka=nt({entries:[],unreadCount:0,pendingSelectId:null}),n=new Map,r=!0,i=null;async function a(){let a=await e.rpc.call(`devframes:plugin:messages:list`,i),o=0;a.full&&n.clear();for(let e of a.removedIds)n.delete(e);for(let e of a.entries){let t=n.get(e.id);t||o++,Oa(e,t,r)&&Ea(e),n.set(e.id,e)}t.entries=Array.from(n.values()),t.unreadCount+=o,i=a.version,r=!1}let o=()=>a().catch(()=>{});return e.rpc.client.register({name:`devframe:messages:updated`,type:`action`,handler:()=>{e.rpc.isTrusted&&o()}}),e.rpc.ensureTrusted().then(()=>o()),t}function ja(e){ka&&(ka.pendingSelectId=e)}var Ma={info:{icon:`i-ph:info-duotone`,color:`text-blue`,bg:`bg-blue`,label:`Info`},warn:{icon:`i-ph:warning-duotone`,color:`text-amber`,bg:`bg-amber`,label:`Warning`},error:{icon:`i-ph:x-circle-duotone`,color:`text-red`,bg:`bg-red`,label:`Error`},success:{icon:`i-ph:check-circle-duotone`,color:`text-green`,bg:`bg-green`,label:`Success`},debug:{icon:`i-ph:bug-duotone`,color:`text-gray`,bg:`bg-gray`,label:`Debug`}};function Na(e,t=1){let n=0;for(let t=0;t({color:Na(t.label),backgroundColor:Na(t.label,.1)}));return(t,r)=>(G(),j(`span`,{class:`text-xs px-1 rounded`,style:R(n.value)},q(e.label),5))}}),Fa={class:`flex items-start gap-2 relative`},Ia={key:0,class:`flex-none mt-0.5 border-2 border-current border-t-transparent rounded-full animate-spin op50 w-4 h-4`},La={class:`flex-1 min-w-0 space-y-0.5`},Ra={class:`flex items-center gap-2`},za=[`title`],Ba={key:0,class:`text-xs op80 whitespace-pre-wrap`},Va={key:1,class:`flex flex-wrap items-center gap-x-2 gap-y-0.5`},Ha=J({__name:`MessageItem`,props:{entry:{},compact:{type:Boolean}},setup(e){let t=e,n=rt(()=>t.entry.timestamp);return(t,r)=>(G(),j(`div`,Fa,[P(`div`,{class:V([`w-2px flex-none absolute left-0 top-4px bottom-4px rounded-r`,[U(Ma)[e.entry.level]?.bg||`bg-gray`]])},null,2),e.entry.status===`loading`?(G(),j(`div`,Ia)):(G(),j(`div`,{key:1,class:V([`flex-none mt-0.5 w-4 h-4`,[U(Ma)[e.entry.level]?.icon,U(Ma)[e.entry.level]?.color]])},null,2)),P(`div`,La,[P(`div`,Ra,[P(`div`,{class:V([`flex-1 min-w-0 truncate text-sm font-medium`,[e.entry.status===`loading`?`op60`:``]])},q(e.entry.message),3),e.compact?A(``,!0):(G(),j(`span`,{key:0,class:`text-xs op40 flex-none`,title:new Date(e.entry.timestamp).toLocaleString()},q(U(n)),9,za)),Pe(t.$slots,`actions`)]),e.entry.description?(G(),j(`div`,Ba,q(e.entry.description),1)):A(``,!0),e.compact?A(``,!0):(G(),j(`div`,Va,[e.entry.category?(G(),F(Pa,{key:0,label:e.entry.category},null,8,[`label`])):A(``,!0),(G(!0),j(H,null,L(e.entry.labels,e=>(G(),F(Pa,{key:e,label:e},null,8,[`label`]))),128))]))])]))}}),Ua={key:0,class:`fixed bottom-4 right-4 z-dock-toast flex flex-col gap-2 pointer-events-auto w-72`},Wa=[`onClick`],Ga=[`onClick`],Ka=[`onClick`],qa=J({__name:`ToastOverlay`,props:{context:{}},setup(e){let t=e;t.context&&Aa(t.context);let n=Ta();function r(e){Da(e),ja(e),t.context?.docks.switchEntry(y)}function i(e){e.kind===`activate`?t.context?.docks.switchEntry(e.activate.dockId):e.kind===`command`&&t.context?.commands.execute(e.command.id,...e.command.params??[])}return(e,t)=>U(n).length>0?(G(),j(`div`,Ua,[I(at,{"enter-active-class":`transition-all duration-300 ease-out`,"leave-active-class":`transition-all duration-200 ease-in`,"enter-from-class":`opacity-0 translate-x-4`,"leave-to-class":`opacity-0 translate-x-4`},{default:z(()=>[(G(!0),j(H,null,L(U(n),e=>(G(),j(`div`,{key:e.id,class:`bg-toast-glass border color-base border-base shadow-xl cursor-pointer transition-colors rounded`,onClick:t=>r(e.id)},[I(Ha,{entry:e.entry,compact:``,class:`px-3 py-2.5`},{actions:z(()=>[(G(!0),j(H,null,L(e.entry.actions,e=>(G(),j(`button`,{key:e.id,class:`flex-none text-xs px-1.5 py-0.5 rounded border border-base op70 hover:op100 hover:bg-active transition`,onClick:Te(t=>i(e),[`stop`])},q(e.label),9,Ga))),128)),P(`button`,{class:`flex-none op30 hover:op100 p-0.5 rounded hover:bg-active transition`,onClick:Te(t=>U(Da)(e.id),[`stop`])},[...t[0]||=[P(`div`,{class:`i-ph-x w-3 h-3`},null,-1)]],8,Ka)]),_:2},1032,[`entry`])],8,Wa))),128))]),_:1})])):A(``,!0)}}),Ja=100,Ya=10,Xa=8;function Za(e,t,n){return Math.min(Math.max(e,t),Math.max(n,t))}function Qa({rect:e,anchorRight:t,anchorBottom:n,vw:r,vh:i}){return e.leftr-Ja?`left`:e.topi-Ja?`top`:`bottom`}function $a(e,t){let{rect:n,anchorRight:r,anchorBottom:i,vw:a,vh:o,gap:s,panelWidth:c,panelHeight:l}=t;return e===`bottom`&&i+s+l>o-Xa&&n.top-s-l>=Xa?`top`:e===`top`&&n.top-s-la-Xa&&n.left-s-c>=Xa?`left`:e===`left`&&n.left-s-ce[t]!==`none`)||/paint|layout|strict|content/.test(e.contain))&&(t=n)}return t?.parentElement??void 0}var io=J({name:`FloatingPopover`,props:{item:{type:Object,required:!1},dismissOnClickOutside:{type:Boolean,default:!0},panelClass:{type:[String,Array],required:!1},ignore:{type:Array,required:!1}},emits:[`dismiss`],setup(e,{emit:t}){let n=K(`panel`),r=B(e.item?.el),i=B(0),a=B();function o(e){a.value=e?ro(e):void 0}let s=nt({width:0,height:0}),c=B(!1);function l(){if(!e.item||!n.value)return;let{width:t,height:r}=n.value.getBoundingClientRect();(Math.abs(t-s.width)>.5||Math.abs(r-s.height)>.5)&&(s.width=t,s.height=r),Ae(()=>{c.value=!0})}Me(()=>{o(e.item?.el),l()}),we(l),Fe(window,`resize`,()=>{r.value&&i.value++}),Fe(window,`scroll`,()=>{r.value&&i.value++},{capture:!0,passive:!0});let u=st(()=>{e.item?.el??(r.value=void 0,s.width=0,s.height=0,c.value=!1)},800);e.dismissOnClickOutside&&We(n,()=>{t(`dismiss`)},{ignore:e.ignore}),Xe(()=>e.item,e=>{e?(r.value===e.el?i.value++:r.value=e.el,o(e.el)):u()});let d,f={},p=e=>a.value?je(Ke,{to:a.value},[e]):e;return()=>{if(i.value,!r.value)return null;let t=c.value?`transition-all duration-300`:`transition-opacity duration-300`;if(!e.item)return p(je(`div`,{ref:`panel`,class:[`fixed z-floating-tooltip text-xs ${t} w-max bg-glass:80 color-base border border-base rounded px2 p1`,`op0 pointer-events-none`,e.panelClass],style:f},d));let{style:n}=to({rect:r.value.getBoundingClientRect(),viewportWidth:window.innerWidth,viewportHeight:window.innerHeight,panelWidth:s.width,panelHeight:s.height,gap:e.item.gap,placement:e.item.placement});f=n;let a=(typeof e.item?.content==`string`?je(`span`,e.item?.content):e.item?.content())??d;return d=a,p(je(`div`,{ref:`panel`,class:[`fixed z-floating-tooltip text-xs ${t} w-max bg-glass:80 color-base border border-base rounded px2 p1`,e.item?`op100`:`op0 pointer-events-none`,e.panelClass],style:n},a))}}}),ao=J({__name:`FloatingElements`,setup(e){let t=de(),n=E(),r=T(),i=ne(),a=ae(),o=b();return Fe(window,`keydown`,e=>{e.key===`Escape`&&a.value&&le(null)}),(e,s)=>(G(),j(H,null,[I(U(io),{item:U(n),onDismiss:s[0]||=()=>U(D)(null)},null,8,[`item`]),I(U(io),{item:U(r),onDismiss:s[1]||=()=>U(O)(null)},null,8,[`item`]),I(U(io),{item:U(i),onDismiss:s[2]||=()=>U(S)(null)},null,8,[`item`]),I(U(io),{item:U(a),onDismiss:s[3]||=()=>U(le)(null)},null,8,[`item`]),I(U(io),{item:U(o),onDismiss:s[4]||=()=>U(C)(null)},null,8,[`item`]),I(U(io),{item:U(t)},null,8,[`item`])],64))}}),oo=[`aria-label`],so=[`value`,`autofocus`,`aria-label`,`aria-invalid`,`disabled`,`onInput`,`onKeydown`,`onPaste`],co=J({__name:`OtpInput`,props:{modelValue:{},length:{default:6},disabled:{type:Boolean,default:!1},invalid:{type:Boolean,default:!1},autofocus:{type:Boolean,default:!0},label:{default:`One-time authorization code`}},emits:[`update:modelValue`,`complete`],setup(e,{expose:t,emit:n}){let r=e,i=n,a=B([]),o=N(()=>Array.from({length:r.length},(e,t)=>r.modelValue[t]??``));function s(e,t){e&&(a.value[t]=e)}function c(e){let t=Math.max(0,Math.min(r.length-1,e));Ae(()=>{let e=a.value[t];e?.focus(),e?.select()})}function l(e){let t=e.replace(/\D/g,``).slice(0,r.length);t!==r.modelValue&&i(`update:modelValue`,t),t.length===r.length&&i(`complete`,t)}function u(e,t){let n=o.value.slice(),i=e;for(let e of t){if(i>=r.length)break;n[i]=e,i++}return l(n.join(``)),i}function d(e,t){let n=t.target.value.replace(/\D/g,``);if(!n){let t=o.value.slice();t[e]=``,l(t.join(``));return}c(u(e,n))}function f(e,t){switch(t.key){case`Backspace`:{t.preventDefault();let n=o.value.slice();n[e]?(n[e]=``,l(n.join(``))):e>0&&(n[e-1]=``,l(n.join(``)),c(e-1));break}case`Delete`:{t.preventDefault();let n=o.value.slice();n[e]=``,l(n.join(``));break}case`ArrowLeft`:t.preventDefault(),c(e-1);break;case`ArrowRight`:t.preventDefault(),c(e+1);break;case`Home`:t.preventDefault(),c(0);break;case`End`:t.preventDefault(),c(r.length-1)}}function p(e,t){t.preventDefault();let n=(t.clipboardData?.getData(`text`)??``).replace(/\D/g,``);n&&c(u(e,n.slice(0,r.length-e)))}function m(e){e.target.select()}function h(e){return r.invalid?`border-red-500/70 text-red-500`:o.value[e]?`border-primary-400/60 dark:border-primary-400/50`:`border-base`}return t({focus(){let e=o.value.findIndex(e=>!e);c(e===-1?r.length-1:e)}}),(t,n)=>(G(),j(`div`,{role:`group`,"aria-label":e.label,class:V([`flex items-center justify-center gap-2 sm:gap-2.5`,e.invalid?`devframes-shake`:``])},[(G(!0),j(H,null,L(o.value,(t,n)=>(G(),j(`input`,{key:n,ref_for:!0,ref:e=>s(e,n),value:t,type:`text`,inputmode:`numeric`,autocomplete:`one-time-code`,"data-1p-ignore":``,"data-lpignore":`true`,"data-bwignore":`true`,"data-form-type":`other`,autofocus:e.autofocus&&n===0,"aria-label":`Digit ${n+1} of ${e.length}`,"aria-invalid":e.invalid||void 0,disabled:e.disabled,maxlength:`1`,class:V([`w-11 h-14 text-center text-2xl font-mono tabular-nums font-medium rounded-xl border bg-base color-base caret-primary-500 outline-none transition-all duration-150 focus:border-primary-500 focus:ring-3 focus:ring-primary-500/25 disabled:op40 disabled:pointer-events-none`,h(n)]),onInput:e=>d(n,e),onKeydown:e=>f(n,e),onPaste:e=>p(n,e),onFocus:m},null,42,so))),128))],10,oo))}}),lo={class:`w-full h-full flex items-center justify-center p8 color-base overflow-auto`},uo={class:`w-full max-w-108 flex flex-col items-center text-center`},fo={class:`relative flex items-center justify-center`},po={class:`text-2xl font-bold tracking-tight`},mo={class:`mt2 text-sm op-fade leading-relaxed max-w-92`},ho={class:`mt6 w-full rounded-lg border border-base bg-base/60 shadow-sm p6 flex flex-col items-center gap-4`},go=6,_o=J({__name:`ViewBuiltinClientAuthNotice`,props:{context:{}},setup(e){let t=e,n=qe(),r=B(``),i=B(``),a=B(!1),o=K(`otp`),s=!1;Xe(r,()=>{if(s){s=!1;return}i.value&&=``});async function c(){let e=r.value;if(!(e.length(G(),j(`div`,lo,[P(`div`,uo,[P(`div`,fo,[t[1]||=P(`div`,{class:`absolute w-24 h-24 rounded-full bg-primary-500/20 blur-2xl`,"aria-hidden":`true`},null,-1),I(ur,{class:`relative w-16 h-16`})]),P(`h1`,po,` Authorize `+q(U(n).productName),1),P(`p`,mo,q(U(n).productName)+` can access your server, read your filesystem, and run commands. Confirm it's you before continuing. `,1),P(`div`,ho,[t[2]||=P(`div`,{class:`flex flex-col items-center gap-1`},[P(`p`,{class:`text-sm op-fade`},[M(` Find the `),P(`span`,{class:`font-mono text-primary-600 dark:text-primary-300`},`6-digit code`),M(` printed in your terminal. `)])],-1),P(`form`,{class:`flex flex-col items-center gap-4 w-full`,autocomplete:`off`,onSubmit:Te(c,[`prevent`])},[I(co,{ref_key:`otp`,ref:o,modelValue:r.value,"onUpdate:modelValue":t[0]||=e=>r.value=e,length:go,invalid:!!i.value,disabled:a.value,label:`Enter your one-time authorization code`,onComplete:c},null,8,[`modelValue`,`invalid`,`disabled`]),I(ma,{type:`submit`,variant:`primary`,icon:`i-ph-shield-check-duotone`,class:`w-full justify-center text-base py-2! disabled:saturate-0`,loading:a.value,disabled:r.value.length[M(q(a.value?`Authorizing`:`Authorize`),1)]),_:1},8,[`loading`,`disabled`]),i.value||a.value?(G(),j(`p`,{key:0,class:V([`text-sm min-h-5 transition-colors`,i.value?`text-red-500`:`op-mute`]),role:`alert`,"aria-live":`assertive`},[i.value?(G(),j(H,{key:0},[M(q(i.value),1)],64)):a.value?(G(),j(H,{key:1},[M(` Authorizing... `)],64)):A(``,!0)],2)):A(``,!0)],32)]),t[3]||=P(`p`,{class:`mt4 text-xs op-mute max-w-92 leading-relaxed`},` You can also use the magic link to authorize automatically. `,-1)])]))}}),vo={class:`flex flex-col gap-6`},yo={class:`flex items-center gap-3 cursor-pointer group`},bo=J({__name:`SettingsAdvanced`,props:{context:{}},setup(e){let t=e,n=oe(t.context),r=t.context.docks.settings,i=qe(),a=ga();async function o(){await a({title:`Reset All Settings`,message:`Reset all settings to defaults? This includes appearance, docks, and shortcuts.`})&&r.mutate(()=>be())}async function s(){await a({title:`Reset Keyboard Shortcuts`,message:`Reset all keyboard shortcuts to defaults?`})&&r.mutate(e=>{e.commandShortcuts={}})}async function c(){await a({title:`Reset Dock Settings`,message:`Reset dock visibility, order, and pinning to defaults?`})&&r.mutate(e=>{let t=be();e.docksHidden=t.docksHidden,e.docksCategoriesHidden=t.docksCategoriesHidden,e.docksCustomOrder=t.docksCustomOrder,e.docksPinned=t.docksPinned})}async function l(){await a({title:`Revoke Authorization`,message:`Revoke this browser's access to ${i.value.productName}? You'll need to authorize again with a new code to reconnect.`})&&await t.context.rpc.call(`devframe:auth:revoke`)}return(e,t)=>(G(),j(`div`,vo,[P(`label`,yo,[P(`button`,{class:V([`w-10 h-6 rounded-full transition-colors relative shrink-0`,U(n).showDevframeInspector?`bg-primary`:`bg-gray/30`]),onClick:t[0]||=e=>U(r).mutate(e=>{e.showDevframeInspector=!U(n).showDevframeInspector})},[P(`div`,{class:V([`absolute top-1 w-4 h-4 rounded-full bg-white shadow transition-transform`,U(n).showDevframeInspector?`translate-x-5`:`translate-x-1`])},null,2)],2),t[1]||=P(`div`,{class:`flex flex-col`},[P(`span`,{class:`text-sm`},`Show Devframe Inspector`),P(`span`,{class:`text-xs op50`},`Reveal the experimental Devframe Inspector dock, the devtools for the devtools`)],-1)]),t[10]||=P(`div`,{class:`border-t border-base`},null,-1),P(`div`,{class:`flex items-start gap-4`},[t[3]||=P(`div`,{class:`flex-1`},[P(`div`,{class:`text-sm`},` Reset Keyboard Shortcuts `),P(`div`,{class:`text-xs op50 mt-0.5`},` Remove all custom shortcut overrides and restore default keybindings `)],-1),P(`button`,{class:`px-4 py-2 rounded bg-orange/10 text-orange hover:bg-orange/20 transition-colors flex items-center gap-2 text-sm shrink-0`,onClick:s},[...t[2]||=[P(`div`,{class:`i-ph-keyboard-duotone w-4 h-4`},null,-1),M(` Reset Shortcuts `,-1)]])]),P(`div`,{class:`flex items-start gap-4`},[t[5]||=P(`div`,{class:`flex-1`},[P(`div`,{class:`text-sm`},` Reset Dock Settings `),P(`div`,{class:`text-xs op50 mt-0.5`},` Restore default dock visibility, order, and pinning `)],-1),P(`button`,{class:`px-4 py-2 rounded bg-orange/10 text-orange hover:bg-orange/20 transition-colors flex items-center gap-2 text-sm shrink-0`,onClick:c},[...t[4]||=[P(`div`,{class:`i-ph-layout-duotone w-4 h-4`},null,-1),M(` Reset Docks `,-1)]])]),P(`div`,{class:`border-t border-base pt-6`},[P(`div`,{class:`flex items-start gap-4`},[t[7]||=P(`div`,{class:`flex-1`},[P(`div`,{class:`text-sm`},` Reset All Settings `),P(`div`,{class:`text-xs op50 mt-0.5`},` Reset everything to defaults including appearance, docks, and shortcuts `)],-1),P(`button`,{class:`px-4 py-2 rounded bg-red/10 text-red hover:bg-red/20 transition-colors flex items-center gap-2 text-sm shrink-0`,onClick:o},[...t[6]||=[P(`div`,{class:`i-ph-arrow-counter-clockwise w-4 h-4`},null,-1),M(` Reset All `,-1)]])])]),P(`div`,{class:`border-t border-base pt-6`},[P(`div`,{class:`flex items-start gap-4`},[t[9]||=P(`div`,{class:`flex-1`},[P(`div`,{class:`text-sm`},` Revoke Authorization `),P(`div`,{class:`text-xs op50 mt-0.5`},` De-authorize this browser and revoke its access token; you'll re-authorize with a new code `)],-1),P(`button`,{class:`px-4 py-2 rounded bg-red/10 text-red hover:bg-red/20 transition-colors flex items-center gap-2 text-sm shrink-0`,onClick:l},[...t[8]||=[P(`div`,{class:`i-ph-sign-out-duotone w-4 h-4`},null,-1),M(` Revoke Access `,-1)]])])])]))}}),xo={class:`flex flex-col gap-4`},So={class:`flex flex-col gap-2`},Co={class:`flex flex-col`},wo={class:`text-xs op50`},To={class:`flex items-center gap-1 bg-gray/10 rounded-lg p1 w-fit`},Eo=[`onClick`],Do={key:0,class:`flex flex-col gap-2`},Oo={class:`flex flex-col`},ko={class:`text-xs op50`},Ao={class:`flex items-center gap-1 bg-gray/10 rounded-lg p1 w-fit`},jo=[`onClick`],Mo={class:`flex items-center gap-3 cursor-pointer group`},No={class:`flex items-center gap-3 cursor-pointer group`},Po={class:`flex flex-col`},Fo={class:`text-xs op50`},Io={class:`flex items-center gap-3 cursor-pointer group`},Lo=J({__name:`SettingsAppearance`,props:{context:{}},setup(e){let t=e,n=oe(t.context),r=t.context.docks.settings,i=qe(),a=t.context.panel.store,o=t.context.clientType===`embedded`,s=ie(),c=N(()=>{let e=[{value:`float`,label:`Float`,icon:`i-ph-cards-three-duotone`},{value:`edge`,label:`Edge`,icon:`i-ph-square-half-bottom-duotone`}];return ue()&&e.push({value:`popup`,label:`Popup`,icon:`i-ph-arrow-square-out-duotone`}),e}),l=N(()=>a.mode),u=[{value:`auto`,label:`Auto`,icon:`i-ph-laptop-duotone`},{value:`light`,label:`Light`,icon:`i-ph-sun-duotone`},{value:`dark`,label:`Dark`,icon:`i-ph-moon-duotone`}];function d(e){e===`popup`?se(t.context):a.mode=e}return(e,t)=>(G(),j(`div`,xo,[P(`div`,So,[P(`div`,Co,[t[3]||=P(`span`,{class:`text-sm`},`Color mode`,-1),P(`span`,wo,`Theme for `+q(U(i).productName)+` and its inner panels`,1)]),P(`div`,To,[(G(),j(H,null,L(u,e=>P(`button`,{key:e.value,class:V([`flex items-center gap-1.5 px3 py1.5 rounded-md text-sm transition-all`,U(Re)===e.value?`bg-base shadow text-primary font-medium`:`op60 hover:op100 hover:bg-gray/10`]),onClick:t=>U(Qe)(e.value)},[P(`div`,{class:V([e.icon,`w-4 h-4`])},null,2),M(` `+q(e.label),1)],10,Eo)),64))])]),o&&!U(s)?(G(),j(`div`,Do,[P(`div`,Oo,[t[4]||=P(`span`,{class:`text-sm`},`Dock mode`,-1),P(`span`,ko,`How the `+q(U(i).productName)+` panel is displayed`,1)]),P(`div`,Ao,[(G(!0),j(H,null,L(c.value,e=>(G(),j(`button`,{key:e.value,class:V([`flex items-center gap-1.5 px3 py1.5 rounded-md text-sm transition-all`,l.value===e.value?`bg-base shadow text-primary font-medium`:`op60 hover:op100 hover:bg-gray/10`]),onClick:t=>d(e.value)},[P(`div`,{class:V([e.icon,`w-4 h-4`])},null,2),M(` `+q(e.label),1)],10,jo))),128))])])):A(``,!0),P(`label`,Mo,[P(`button`,{class:V([`w-10 h-6 rounded-full transition-colors relative shrink-0`,U(n).showIframeAddressBar?`bg-primary`:`bg-gray/30`]),onClick:t[0]||=e=>U(r).mutate(e=>{e.showIframeAddressBar=!U(n).showIframeAddressBar})},[P(`div`,{class:V([`absolute top-1 w-4 h-4 rounded-full bg-white shadow transition-transform`,U(n).showIframeAddressBar?`translate-x-5`:`translate-x-1`])},null,2)],2),t[5]||=P(`div`,{class:`flex flex-col`},[P(`span`,{class:`text-sm`},`Always show iframe address bars`),P(`span`,{class:`text-xs op50`},`Show the address bar for every iframe dock.`)],-1)]),P(`label`,No,[P(`button`,{class:V([`w-10 h-6 rounded-full transition-colors relative shrink-0`,U(n).closeOnOutsideClick?`bg-primary`:`bg-gray/30`]),onClick:t[1]||=e=>U(r).mutate(e=>{e.closeOnOutsideClick=!U(n).closeOnOutsideClick})},[P(`div`,{class:V([`absolute top-1 w-4 h-4 rounded-full bg-white shadow transition-transform`,U(n).closeOnOutsideClick?`translate-x-5`:`translate-x-1`])},null,2)],2),P(`div`,Po,[t[6]||=P(`span`,{class:`text-sm`},`Close panel on outside click`,-1),P(`span`,Fo,`Close the `+q(U(i).productName)+` panel when clicking outside of it (embedded mode only)`,1)])]),P(`label`,Io,[P(`button`,{class:V([`w-10 h-6 rounded-full transition-colors relative shrink-0`,U(n).autoCollapseEdgeToolbar?`bg-primary`:`bg-gray/30`]),onClick:t[2]||=e=>U(r).mutate(e=>{e.autoCollapseEdgeToolbar=!U(n).autoCollapseEdgeToolbar})},[P(`div`,{class:V([`absolute top-1 w-4 h-4 rounded-full bg-white shadow transition-transform`,U(n).autoCollapseEdgeToolbar?`translate-x-5`:`translate-x-1`])},null,2)],2),t[7]||=P(`div`,{class:`flex flex-col`},[P(`span`,{class:`text-sm`},`Auto-collapse edge toolbar`),P(`span`,{class:`text-xs op50`},`Shrink the edge-docked toolbar to a small handle when idle and closed (edge mode only)`)],-1)])]))}}),Ro={ref:`sortContainer`,class:`flex flex-col gap-4`},zo={class:`flex items-center gap-2 px-4 py-3 bg-gray/5 cursor-pointer select-none border-b border-base`},Bo=[`disabled`,`onClick`],Vo={class:`font-medium capitalize`},Ho={class:`text-xs op40`},Uo=[`onClick`],Wo=[`data-dock-id`,`data-container`],Go=[`disabled`,`title`,`onClick`],Ko={key:0,class:`i-ph-check-bold text-xs`},qo={class:`flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity`},Jo=[`onClick`],Yo=[`onClick`],Xo=[`title`,`onClick`],Zo={class:`bg-gray/3 flex flex-col w-full flex-auto`},Qo={key:0,class:`px-2 py-2 text-xs op40 italic border-b border-base border-t-0`},$o={key:0,class:`flex items-center gap-2 px-2 py-1.5 bg-gray/5 border-b border-base border-t-0`},es={class:`text-xs uppercase tracking-wide op50`},ts=[`onClick`],ns=[`data-dock-id`,`data-container`],rs=[`title`,`onClick`],is={key:0,class:`i-ph-check-bold text-xs`},as={class:`flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity`},os=[`onClick`],ss=[`onClick`],cs=[`title`,`onClick`],ls=`:::`,us=4,ds=J({__name:`SettingsDocks`,props:{context:{}},setup(e){let t=e,r=oe(t.context),i=t.context.docks.settings,a=N(()=>s(t.context.docks.entries,r.value,{includeHidden:!0,collapseGroups:!0}));function o(e){return n(t.context.docks.entries,e,r.value,{includeHidden:!0})}function c(e){return v(t.context.docks.entries,e,r.value,{includeHidden:!0})}let l=e=>`cat:${e}`,u=(e,t)=>`grp:${e}${ls}${t}`;function d(e){let t=e.slice(4),n=t.indexOf(ls);return{groupId:t.slice(0,n),subcategory:t.slice(n+3)}}function f(e){if(e.startsWith(`grp:`)){let{groupId:t,subcategory:n}=d(e);return c(t).find(([e])=>e===n)?.[1]??[]}let t=e.slice(4);return a.value.find(([e])=>e===t)?.[1]??[]}function p(e){let n={...r.value,docksCustomOrder:{}};if(e.startsWith(`grp:`)){let{groupId:r,subcategory:i}=d(e);return v(t.context.docks.entries,r,n,{includeHidden:!0}).find(([e])=>e===i)?.[1]??[]}let i=e.slice(4);return s(t.context.docks.entries,n,{includeHidden:!0,collapseGroups:!0}).find(([e])=>e===i)?.[1]??[]}let h=K(`sortContainer`),g=new Map,y=B(null),b=B(null),x=B(null),S=B(!1),C=0;function w(e){let t=e.target;for(;t&&t!==h.value;){if(t.dataset.dockId)return{dockId:t.dataset.dockId,container:t.dataset.container??null};t=t.parentElement}return{dockId:null,container:null}}function T(e){for(;e&&e!==h.value;){if(e.tagName===`BUTTON`||e.tagName===`A`||e.tagName===`INPUT`)return!0;e=e.parentElement}return!1}function E(e,t){let n=p(e).map(e=>e.id),r=t.length===n.length&&t.every((e,t)=>e.id===n[t]);i.mutate(e=>{t.forEach((t,n)=>{r?delete e.docksCustomOrder[t.id]:e.docksCustomOrder[t.id]=n})})}He(h,{draggingElement:()=>h.value?.ownerDocument?.defaultView??window,onStart(e,t){if(t.button!==0||T(t.target))return!1;let{dockId:n,container:r}=w(t);if(!n||!r)return!1;y.value=n,b.value=r,S.value=!1,C=t.clientY},onMove(e,t){if(!y.value)return;if(!S.value){if(Math.abs(t.clientY-C)=a.top&&t.clientY<=a.bottom){n=e;break}}x.value=n},onEnd(){if(y.value&&S.value&&x.value&&b.value){let e=[...f(b.value)],t=e.findIndex(e=>e.id===y.value),n=e.findIndex(e=>e.id===x.value);t!==-1&&n!==-1&&(e.splice(n,0,e.splice(t,1)[0]),E(b.value,e))}y.value=null,b.value=null,x.value=null,S.value=!1}});function ee(e,t,n){e?g.set(t,{el:e,container:n}):g.delete(t)}function te(e,t){if(e===`~settings`)return;let n=r.value.docksHidden.includes(e);t??n?i.mutate(t=>{t.docksHidden=t.docksHidden.filter(t=>t!==e)}):i.mutate(t=>{t.docksHidden=[...t.docksHidden,e]})}function D(e,t){if(!_(e))return;let n=r.value.docksCategoriesHidden.includes(e);t??n?i.mutate(t=>{t.docksCategoriesHidden=t.docksCategoriesHidden.filter(t=>t!==e)}):i.mutate(t=>{t.docksCategoriesHidden=[...t.docksCategoriesHidden,e]})}function ne(e){r.value.docksPinned.includes(e)?i.mutate(t=>{t.docksPinned=t.docksPinned.filter(t=>t!==e)}):i.mutate(t=>{t.docksPinned=[...t.docksPinned,e]})}function re(e,t,n){let r=[...f(e)],i=r.findIndex(e=>e.id===t),a=i+n;a<0||a>=r.length||(r.splice(a,0,r.splice(i,1)[0]),E(e,r))}function ie(e){let t=f(e),n=t.map(e=>e.id);if(e.startsWith(`cat:`))for(let e of t)e.type===`group`&&n.push(...o(e.id).map(e=>e.id));return n}function ae(e){let t=f(e).map(e=>e.id),n=p(e).map(e=>e.id);return t.length!==n.length||t.some((e,t)=>e!==n[t])?!0:e.startsWith(`cat:`)?f(e).some(e=>e.type===`group`&&c(e.id).some(([t])=>ae(u(e.id,t)))):!1}function se(e){let t=ie(e);i.mutate(e=>{t.forEach(t=>{delete e.docksCustomOrder[t]})})}return(e,t)=>(G(),j(H,null,[t[11]||=P(`p`,{class:`text-sm op50 mb-4`},` Manage visibility and order of dock entries. Hidden entries will not appear in the dock bar. `,-1),P(`div`,Ro,[(G(!0),j(H,null,L(a.value,([e,n])=>(G(),j(`div`,{key:e,class:V([`border border-base rounded-lg overflow-hidden transition-opacity`,U(r).docksCategoriesHidden.includes(e)?`op40`:``])},[P(`div`,zo,[P(`button`,{class:V([`w-5 h-5 flex items-center justify-center rounded transition-colors`,[U(_)(e)?U(r).docksCategoriesHidden.includes(e)?`bg-gray/20`:`bg-primary/20 text-primary`:`bg-gray/20 cursor-not-allowed op50`]]),disabled:!U(_)(e),onClick:t=>D(e)},[P(`div`,{class:V([`transition-transform`,U(r).docksCategoriesHidden.includes(e)?`i-ph-eye-slash text-sm op50`:`i-ph-check-bold text-xs`])},null,2)],10,Bo),P(`span`,Vo,q(U(m)(e)),1),P(`span`,Ho,`(`+q(n.length)+`)`,1),t[1]||=P(`span`,{class:`flex-auto`},null,-1),ae(l(e))?(G(),j(`button`,{key:0,class:`w-6 h-6 flex items-center justify-center rounded hover:bg-gray/20 transition-colors`,title:`Reset custom order`,onClick:t=>se(l(e))},[...t[0]||=[P(`div`,{class:`i-ph-arrows-counter-clockwise-duotone text-sm op60`},null,-1)]],8,Uo)):A(``,!0)]),P(`div`,null,[(G(!0),j(H,null,L(n,(i,a)=>(G(),j(H,{key:i.id},[P(`div`,{ref_for:!0,ref:t=>ee(t,i.id,l(e)),"data-dock-id":i.id,"data-container":l(e),class:V([`flex items-center gap-3 px-2 py-2.5 hover:bg-gray/5 transition-all group border-b border-base border-t-0`,[U(r).docksHidden.includes(i.id)?`op40`:``,S.value&&y.value===i.id?`op30 bg-gray/10`:``,x.value===i.id?`ring-1.5 ring-purple/50 rounded`:``,S.value?`select-none`:``]])},[P(`div`,{class:`i-ph-dots-six-vertical w-4 h-4 shrink-0 op25 group-hover:op50 transition-opacity cursor-grab`,style:R(S.value&&y.value===i.id?`cursor: grabbing`:``)},null,4),P(`button`,{class:V([`w-6 h-6 flex items-center justify-center rounded border border-transparent hover:border-base transition-colors shrink-0`,i.id===`~settings`?`cursor-not-allowed op50`:U(r).docksHidden.includes(i.id)?`op50`:``]),disabled:i.id===`~settings`,title:i.id===`~settings`?`Always visible`:U(r).docksHidden.includes(i.id)?`Show`:`Hide`,onClick:e=>te(i.id)},[P(`div`,{class:V([`w-4 h-4 rounded flex items-center justify-center transition-colors`,i.id===`~settings`||U(r).docksHidden.includes(i.id)?`bg-gray/30`:`bg-primary/20 text-primary`])},[U(r).docksHidden.includes(i.id)?A(``,!0):(G(),j(`div`,Ko))],2)],10,Go),I(Ui,{icon:i.icon,title:i.title,class:V([`w-5 h-5 shrink-0`,U(r).docksHidden.includes(i.id)?`saturate-0`:``])},null,8,[`icon`,`title`,`class`]),P(`span`,{class:V([`truncate`,U(r).docksHidden.includes(i.id)?`line-through op60`:``])},q(i.title),3),i.type===`action`?(G(),F(Pa,{key:0,label:`Action`,class:`flex-none text-xs`})):i.type===`group`?(G(),F(Pa,{key:1,label:`Group`,class:`flex-none text-xs`})):A(``,!0),t[4]||=P(`div`,{class:`flex flex-auto`},null,-1),P(`div`,qo,[a>0?(G(),j(`button`,{key:0,class:`w-6 h-6 flex items-center justify-center rounded hover:bg-gray/20 transition-colors`,title:`Move up (higher priority)`,onClick:t=>re(l(e),i.id,-1)},[...t[2]||=[P(`div`,{class:`i-ph-caret-up text-sm op60`},null,-1)]],8,Jo)):A(``,!0),are(l(e),i.id,1)},[...t[3]||=[P(`div`,{class:`i-ph-caret-down text-sm op60`},null,-1)]],8,Yo)):A(``,!0)]),P(`button`,{class:V([`w-7 h-7 flex items-center justify-center rounded hover:bg-gray/20 transition-colors shrink-0`,U(r).docksPinned.includes(i.id)?`text-amber`:`op40 hover:op70`]),title:U(r).docksPinned.includes(i.id)?`Unpin`:`Pin`,onClick:e=>ne(i.id)},[P(`div`,{class:V([U(r).docksPinned.includes(i.id)?`i-ph-push-pin-fill rotate--45`:`i-ph-push-pin`,`color-base`])},null,2)],10,Xo)],10,Wo),i.type===`group`?(G(),j(`div`,{key:0,class:V([`flex`,U(r).docksHidden.includes(i.id)?`op50`:``])},[t[10]||=P(`div`,{class:`border-base border-r border-b w-6 flex-none`},null,-1),P(`div`,Zo,[o(i.id).length===0?(G(),j(`div`,Qo,` No tools in this group yet `)):A(``,!0),(G(!0),j(H,null,L(c(i.id),([e,n])=>(G(),j(H,{key:e},[c(i.id).length>1?(G(),j(`div`,$o,[P(`span`,es,q(U(m)(e)),1),t[6]||=P(`span`,{class:`flex-auto`},null,-1),ae(u(i.id,e))?(G(),j(`button`,{key:0,class:`w-5 h-5 flex items-center justify-center rounded hover:bg-gray/20 transition-colors`,title:`Reset custom order`,onClick:t=>se(u(i.id,e))},[...t[5]||=[P(`div`,{class:`i-ph-arrows-counter-clockwise-duotone text-xs op60`},null,-1)]],8,ts)):A(``,!0)])):A(``,!0),(G(!0),j(H,null,L(n,(a,o)=>(G(),j(`div`,{key:a.id,ref_for:!0,ref:t=>ee(t,a.id,u(i.id,e)),"data-dock-id":a.id,"data-container":u(i.id,e),class:V([`flex items-center gap-3 px-2 py-2 hover:bg-gray/5 transition-all group border-b border-base border-t-0`,[U(r).docksHidden.includes(a.id)?`op40`:``,S.value&&y.value===a.id?`op30 bg-gray/10`:``,x.value===a.id?`ring-1.5 ring-purple/50 rounded`:``,S.value?`select-none`:``]])},[P(`div`,{class:`i-ph-dots-six-vertical w-4 h-4 shrink-0 op25 group-hover:op50 transition-opacity cursor-grab`,style:R(S.value&&y.value===a.id?`cursor: grabbing`:``)},null,4),P(`button`,{class:V([`w-6 h-6 flex items-center justify-center rounded border border-transparent hover:border-base transition-colors shrink-0`,U(r).docksHidden.includes(a.id)?`op50`:``]),title:U(r).docksHidden.includes(a.id)?`Show`:`Hide`,onClick:e=>te(a.id)},[P(`div`,{class:V([`w-4 h-4 rounded flex items-center justify-center transition-colors`,U(r).docksHidden.includes(a.id)?`bg-gray/30`:`bg-primary/20 text-primary`])},[U(r).docksHidden.includes(a.id)?A(``,!0):(G(),j(`div`,is))],2)],10,rs),I(Ui,{icon:a.icon,title:a.title,class:V([`w-5 h-5 shrink-0`,U(r).docksHidden.includes(a.id)?`saturate-0`:``])},null,8,[`icon`,`title`,`class`]),P(`span`,{class:V([`truncate text-sm`,U(r).docksHidden.includes(a.id)?`line-through op60`:``])},q(a.title),3),a.type===`action`?(G(),F(Pa,{key:0,label:`Action`,class:`flex-none text-xs`})):A(``,!0),t[9]||=P(`div`,{class:`flex flex-auto`},null,-1),P(`div`,as,[o>0?(G(),j(`button`,{key:0,class:`w-6 h-6 flex items-center justify-center rounded hover:bg-gray/20 transition-colors`,title:`Move up (higher priority)`,onClick:t=>re(u(i.id,e),a.id,-1)},[...t[7]||=[P(`div`,{class:`i-ph-caret-up text-sm op60`},null,-1)]],8,os)):A(``,!0),ore(u(i.id,e),a.id,1)},[...t[8]||=[P(`div`,{class:`i-ph-caret-down text-sm op60`},null,-1)]],8,ss)):A(``,!0)]),P(`button`,{class:V([`w-7 h-7 flex items-center justify-center rounded hover:bg-gray/20 transition-colors shrink-0`,U(r).docksPinned.includes(a.id)?`text-amber`:`op40 hover:op70`]),title:U(r).docksPinned.includes(a.id)?`Unpin`:`Pin`,onClick:e=>ne(a.id)},[P(`div`,{class:V([U(r).docksPinned.includes(a.id)?`i-ph-push-pin-fill rotate--45`:`i-ph-push-pin`,`color-base`])},null,2)],10,cs)],10,ns))),128))],64))),128))])],2)):A(``,!0)],64))),128))])],2))),128))],512)],64))}}),fs={class:`mb-3`},ps={class:`border border-base rounded-lg overflow-hidden`},ms={class:`flex-1 min-w-0`},hs={class:`flex items-center gap-1.5`},gs={class:`truncate text-sm`},_s={key:0,class:`text-[10px] px-1.5 py-0.5 rounded bg-blue/10 text-blue shrink-0`},vs={key:0,class:`text-xs op40 truncate mt-0.5`},ys={key:2,class:`flex items-center gap-1.5 shrink-0`},bs=[`onClick`],xs=[`onClick`],Ss=[`onClick`],Cs=[`onClick`],ws={key:0,class:`py-6 text-center text-sm op40`},Ts={class:`relative bg-base border border-base rounded-lg shadow-xl w-96 p-5`},Es={class:`mb-4`},Ds=[`value`],Os={class:`flex items-center gap-2 mb-4`},ks={key:0,class:`mb-4 flex flex-col gap-1.5`},As={class:`flex items-center justify-end gap-2`},js=[`disabled`],Ms=J({__name:`SettingsShortcuts`,props:{context:{}},setup(e){let n=e,r=n.context.commands,a=oe(n.context),o=N(()=>a.value.commandShortcuts??{}),s=B(``),c=N(()=>d(r.commands,{...n.context.when.context,dockOpen:!0,paletteOpen:!1})),l=N(()=>{let e=[];return f(c.value,(t,n)=>{let r=n.at(-1)?.title;e.push({command:t,...r?{parentTitle:r}:{},depth:n.length})}),e}),p=N(()=>{if(!s.value)return l.value;let e=s.value.toLowerCase();return l.value.filter(t=>t.command.title.toLowerCase().includes(e)||t.command.id.toLowerCase().includes(e)||t.command.description?.toLowerCase().includes(e))});function m(e){return r.getKeybindings(e)}function h(e){return e.depth>0?{marginLeft:`${e.depth*1.5}rem`}:{}}function g(e){return e.source===`server`||!!e.action}function _(e){return ee(o.value[e],v(e))}function v(e){return te(r.commands,e)?.keybindings??[]}function y(e){r.settings.mutate(t=>{v(e).length>0?t.commandShortcuts[e]=[]:delete t.commandShortcuts[e]})}function b(e){r.settings.mutate(t=>{delete t.commandShortcuts[e]})}let x=B(!1),S=B(null),C=B(!1),w=B(!1),T=B(!1),E=B(!1),D=B(``);function ne(e){let t=m(e);if(S.value=e,t.length>0){let e=t[0].key.split(`+`);C.value=e.includes(`Mod`),w.value=e.includes(`Ctrl`),T.value=e.includes(`Alt`),E.value=e.includes(`Shift`),D.value=e.filter(e=>![`Mod`,`Ctrl`,`Alt`,`Shift`,`Meta`].includes(e)).join(`+`)}else C.value=!1,w.value=!1,T.value=!1,E.value=!1,D.value=``;x.value=!0}function re(){x.value=!1,S.value=null}let ie=N(()=>{let e=[];return C.value&&e.push(`Mod`),w.value&&e.push(`Ctrl`),T.value&&e.push(`Alt`),E.value&&e.push(`Shift`),D.value&&e.push(D.value),e.join(`+`)}),ae=N(()=>{let e=[],n=ie.value;if(!n)return e;let r=C.value||w.value||T.value;!r&&D.value&&e.push(`Single key without modifiers may interfere with typing`),!r&&E.value&&D.value.length===1&&e.push(`Shift + letter may interfere with typing`);let a=i[n];if(a){let r=t(n).join(`+`);e.push(`Conflicts with browser shortcut: ${a} (${r})`)}if(S.value){for(let t of l.value)if(t.command.id!==S.value&&m(t.command.id).some(e=>e.key===n)){e.push(`Conflicts with "${t.command.title}"`);break}}return e}),se=N(()=>D.value.length>0);function ce(e){if(e.preventDefault(),e.stopPropagation(),[`Control`,`Meta`,`Alt`,`Shift`].includes(e.key))return;C.value=u?e.metaKey:e.ctrlKey,u&&e.ctrlKey&&(w.value=!0),T.value=e.altKey,E.value=e.shiftKey;let t=e.key;t.length===1&&(t=t.toUpperCase()),D.value=t}function le(){if(!S.value||!se.value)return;let e=[{key:ie.value}];ee(e,v(S.value))?r.settings.mutate(t=>{t.commandShortcuts[S.value]=e}):r.settings.mutate(e=>{delete e.commandShortcuts[S.value]}),re()}let ue=B(null);return Xe(x,async e=>{if(!e)return;await Ae();let t=ue.value?.ownerDocument??document,n=t.defaultView??window,r=e=>{if(e.key===`Escape`){let e=t.activeElement;(!e||!e.classList.contains(`shortcut-key-input`))&&(re(),n.removeEventListener(`keydown`,r))}};n.addEventListener(`keydown`,r)}),(e,n)=>(G(),j(H,null,[n[10]||=P(`p`,{class:`text-sm op50 mb-4`},` Customize keyboard shortcuts for commands. `,-1),P(`div`,fs,[Ze(P(`input`,{"onUpdate:modelValue":n[0]||=e=>s.value=e,class:`w-full bg-gray/10 rounded-lg px-3 py-2 text-sm outline-none focus:ring-1 focus:ring-primary/30`,placeholder:`Search commands...`},null,512),[[ke,s.value]])]),P(`div`,ps,[(G(!0),j(H,null,L(p.value,e=>(G(),j(`div`,{key:e.command.id,class:`flex items-center gap-3 px-4 py-2.5 hover:bg-gray/5 transition-colors border-b border-base last:border-b-0`},[e.command.icon?(G(),F(Ui,{key:0,icon:e.command.icon,class:`w-4 h-4 shrink-0 op60`,style:R(h(e))},null,8,[`icon`,`style`])):(G(),j(`div`,{key:1,style:R(h(e)),class:`w-4 h-4 shrink-0`},null,4)),P(`div`,ms,[P(`div`,hs,[P(`span`,gs,q(e.command.title),1),e.command.source===`server`?(G(),j(`span`,_s,`server`)):A(``,!0)]),e.command.description?(G(),j(`div`,vs,q(e.command.description),1)):A(``,!0)]),g(e.command)?(G(),j(`div`,ys,[m(e.command.id).length>0?(G(!0),j(H,{key:0},L(m(e.command.id),(t,n)=>(G(),j(`button`,{key:n,class:`flex items-center gap-0.5 hover:bg-gray/10 rounded px-1 py-0.5 transition-colors`,title:`Click to edit`,onClick:t=>ne(e.command.id)},[I(sr,{keys:t.key},null,8,[`keys`])],8,bs))),128)):(G(),j(`button`,{key:1,class:`text-xs op30 hover:op60 px-2 py-1 rounded hover:bg-gray/10 transition-colors`,onClick:t=>ne(e.command.id)},` Add shortcut `,8,xs)),m(e.command.id).length>0?(G(),j(`button`,{key:2,class:`w-5 h-5 flex items-center justify-center rounded hover:bg-gray/20 transition-colors op30 hover:op70`,title:`Clear shortcut`,onClick:t=>y(e.command.id)},[...n[5]||=[P(`div`,{class:`i-ph-x text-xs`},null,-1)]],8,Ss)):A(``,!0),_(e.command.id)?(G(),j(`button`,{key:3,class:`w-5 h-5 flex items-center justify-center rounded hover:bg-gray/20 transition-colors op30 hover:op70`,title:`Reset to default`,onClick:t=>b(e.command.id)},[...n[6]||=[P(`div`,{class:`i-ph-arrow-counter-clockwise text-xs`},null,-1)]],8,Cs)):A(``,!0)])):A(``,!0)]))),128)),p.value.length===0?(G(),j(`div`,ws,` No commands found `)):A(``,!0)]),x.value?(G(),j(`div`,{key:0,ref_key:`editorModal`,ref:ue,class:`fixed inset-0 z-command-palette flex items-center justify-center`},[P(`div`,{class:`absolute inset-0 bg-black/30`,onClick:re}),P(`div`,Ts,[n[9]||=P(`h3`,{class:`text-sm font-medium mb-4`},` Edit Shortcut `,-1),P(`div`,Es,[n[7]||=P(`label`,{class:`text-xs op50 mb-1.5 block`},`Press a key combination or toggle modifiers below`,-1),P(`input`,{class:`shortcut-key-input w-full bg-gray/10 border border-base rounded-lg px-3 py-2.5 text-center text-sm font-mono outline-none focus:border-primary/40`,value:ie.value?U(t)(ie.value).join(` + `):``,placeholder:`Press keys...`,readonly:``,onKeydown:ce},null,40,Ds)]),P(`div`,Os,[P(`button`,{class:V([`px-3 py-1.5 rounded-md text-xs font-mono border transition-colors`,C.value?`bg-primary/15 border-primary/30 text-primary`:`border-base op50 hover:op80`]),onClick:n[1]||=e=>C.value=!C.value},q(U(u)?`⌘ Cmd`:`Ctrl`),3),U(u)?(G(),j(`button`,{key:0,class:V([`px-3 py-1.5 rounded-md text-xs font-mono border transition-colors`,w.value?`bg-primary/15 border-primary/30 text-primary`:`border-base op50 hover:op80`]),onClick:n[2]||=e=>w.value=!w.value},` \\u2303 Ctrl `,2)):A(``,!0),P(`button`,{class:V([`px-3 py-1.5 rounded-md text-xs font-mono border transition-colors`,T.value?`bg-primary/15 border-primary/30 text-primary`:`border-base op50 hover:op80`]),onClick:n[3]||=e=>T.value=!T.value},q(U(u)?`⌥ Alt`:`Alt`),3),P(`button`,{class:V([`px-3 py-1.5 rounded-md text-xs font-mono border transition-colors`,E.value?`bg-primary/15 border-primary/30 text-primary`:`border-base op50 hover:op80`]),onClick:n[4]||=e=>E.value=!E.value},q(U(u)?`⇧ Shift`:`Shift`),3)]),ae.value.length>0?(G(),j(`div`,ks,[(G(!0),j(H,null,L(ae.value,(e,t)=>(G(),j(`div`,{key:t,class:`flex items-center gap-1.5 text-xs text-amber`},[n[8]||=P(`div`,{class:`i-ph-warning-duotone w-3.5 h-3.5 shrink-0`},null,-1),M(` `+q(e),1)]))),128))])):A(``,!0),P(`div`,As,[P(`button`,{class:`px-3 py-1.5 rounded text-xs op60 hover:op100 hover:bg-gray/10 transition-colors`,onClick:re},` Cancel `),P(`button`,{class:V([`px-3 py-1.5 rounded text-xs transition-colors`,se.value?`bg-primary/15 text-primary hover:bg-primary/25`:`op30 cursor-not-allowed`]),disabled:!se.value,onClick:le},` OK `,10,js)])])],512)):A(``,!0)],64))}}),Ns={class:`h-full w-full overflow-hidden flex flex-col`},Ps={class:`flex items-center border-b border-base px-6 shrink-0`},Fs=[`onClick`],Is={key:0,class:`absolute bottom-0 left-2 right-2 h-0.5 bg-primary rounded-full`},Ls={class:`flex-1 overflow-auto p8`,style:{"scrollbar-gutter":`stable`}},Rs={class:`max-w-200 mx-auto`},zs=J({__name:`ViewBuiltinSettings`,props:{context:{},entry:{}},setup(e){let t=[{id:`appearance`,label:`Appearance`,icon:`i-ph-paint-brush-duotone`},{id:`docks`,label:`Docks`,icon:`i-ph-layout-duotone`},{id:`shortcuts`,label:`Shortcuts`,icon:`i-ph-keyboard-duotone`},{id:`advanced`,label:`Advanced`,icon:`i-ph-wrench-duotone`}],n=B(`appearance`);return(r,i)=>(G(),j(`div`,Ns,[P(`div`,Ps,[(G(),j(H,null,L(t,e=>P(`button`,{key:e.id,class:V([`flex items-center gap-1.5 px-4 py-3 text-sm transition-colors relative`,n.value===e.id?`op100 text-primary`:`op50 hover:op80`]),onClick:t=>n.value=e.id},[P(`div`,{class:V([e.icon,`w-4 h-4`])},null,2),M(` `+q(e.label)+` `,1),n.value===e.id?(G(),j(`div`,Is)):A(``,!0)],10,Fs)),64))]),P(`div`,Ls,[P(`div`,Rs,[n.value===`appearance`?(G(),F(Lo,{key:0,context:e.context},null,8,[`context`])):A(``,!0),n.value===`shortcuts`?(G(),F(Ms,{key:1,context:e.context},null,8,[`context`])):A(``,!0),n.value===`docks`?(G(),F(ds,{key:2,context:e.context},null,8,[`context`])):A(``,!0),n.value===`advanced`?(G(),F(bo,{key:3,context:e.context},null,8,[`context`])):A(``,!0)])])]))}}),Bs=J({__name:`ViewCustomRenderer`,props:{context:{},entry:{},panes:{},divStyle:{}},setup(e){let t=e,n=K(`viewFrame`);return Me(()=>{let e=t.panes.ensure(t.entry.id,{tagName:`div`}),r=e.element;r.style.boxShadow=`none`,r.style.outline=`none`,Object.assign(r.style,t.divStyle);let i=t.context.docks.getStateById(t.entry.id);i&&(i.domElements.panel=r),e.mount(n.value)}),xe(()=>{t.panes.get(t.entry.id)?.unmount()}),(e,t)=>(G(),j(`div`,{ref_key:`viewFrame`,ref:n,class:`devframes-view-custom-renderer w-full h-full flex items-center justify-center`},null,512))}}),Vs={class:`devframes-view-dock-renderer relative h-full w-full`},Hs={key:0,class:`absolute inset-0 flex flex-col items-center justify-center gap-2 p6 text-center`},Us={class:`text-sm color-muted`},Ws={key:1,class:`absolute inset-0 flex flex-col items-center justify-center gap-2 p6 text-center`},Gs={class:`text-sm color-muted`},Ks={key:0,class:`max-w-100 text-xs color-faint`},qs=J({__name:`ViewDockRenderer`,props:{context:{},entry:{}},setup(e){let t=e,n=K(`container`),r=B(`loading`),i=B(null),a,o=0;async function s(){let e=++o;if(a?.(),a=void 0,!t.context.renderers.has(t.entry.type)){r.value=`missing-renderer`;return}r.value=`loading`,i.value=null;let s=await t.context.renderers.mount(t.entry,n.value);if(e!==o){s.status===`mounted`&&s.dispose();return}s.status===`mounted`?(a=s.dispose,r.value=`mounted`):s.status===`missing-renderer`?r.value=`missing-renderer`:(r.value=`load-error`,i.value=String(s.error?.message??s.error))}return Me(()=>{s()}),Xe(()=>t.entry.id,()=>{s()}),xe(()=>{o++,a?.(),a=void 0}),(t,a)=>(G(),j(`div`,Vs,[Ze(P(`div`,{ref_key:`container`,ref:n,class:V([`h-full w-full`,U(Be)?`dark`:`light`])},null,2),[[k,r.value===`mounted`||r.value===`loading`]]),r.value===`missing-renderer`?(G(),j(`div`,Hs,[a[1]||=P(`div`,{class:`i-ph:puzzle-piece-duotone text-3xl color-faint`},null,-1),P(`div`,Us,` No renderer for “`+q(e.entry.type)+`” in the current environment `,1),a[2]||=P(`div`,{class:`text-xs color-faint`},` The host has not registered a renderer for this dock type. `,-1)])):r.value===`load-error`?(G(),j(`div`,Ws,[a[4]||=P(`div`,{class:`i-ph:warning-duotone text-3xl color-faint`},null,-1),P(`div`,Gs,` The renderer for “`+q(e.entry.type)+`” failed to load `,1),i.value?(G(),j(`div`,Ks,q(i.value),1)):A(``,!0),I(ma,{class:`mt-2`,onClick:a[0]||=e=>s()},{default:z(()=>[...a[3]||=[M(` Retry `,-1)]]),_:1})])):A(``,!0)]))}}),Js=[`top`,`right`,`bottom`,`left`],Ys=[`start`,`end`],Xs=Js.reduce((e,t)=>e.concat(t,t+`-`+Ys[0],t+`-`+Ys[1]),[]),Zs=Math.min,Qs=Math.max,$s={left:`right`,right:`left`,bottom:`top`,top:`bottom`};function ec(e,t,n){return Qs(e,Zs(t,n))}function tc(e,t){return typeof e==`function`?e(t):e}function nc(e){return e.split(`-`)[0]}function rc(e){return e.split(`-`)[1]}function ic(e){return e===`x`?`y`:`x`}function ac(e){return e===`y`?`height`:`width`}function oc(e){let t=e[0];return t===`t`||t===`b`?`y`:`x`}function sc(e){return ic(oc(e))}function cc(e,t,n){n===void 0&&(n=!1);let r=rc(e),i=sc(e),a=ac(i),o=i===`x`?r===(n?`end`:`start`)?`right`:`left`:r===`start`?`bottom`:`top`;return t.reference[a]>t.floating[a]&&(o=_c(o)),[o,_c(o)]}function lc(e){let t=_c(e);return[uc(e),t,uc(t)]}function uc(e){return e.includes(`start`)?e.replace(`start`,`end`):e.replace(`end`,`start`)}var dc=[`left`,`right`],fc=[`right`,`left`],pc=[`top`,`bottom`],mc=[`bottom`,`top`];function hc(e,t,n){switch(e){case`top`:case`bottom`:return n?t?fc:dc:t?dc:fc;case`left`:case`right`:return t?pc:mc;default:return[]}}function gc(e,t,n,r){let i=rc(e),a=hc(nc(e),n===`start`,r);return i&&(a=a.map(e=>e+`-`+i),t&&(a=a.concat(a.map(uc)))),a}function _c(e){let t=nc(e);return $s[t]+e.slice(t.length)}function vc(e){return{top:e.top??0,right:e.right??0,bottom:e.bottom??0,left:e.left??0}}function yc(e){return typeof e==`number`?{top:e,right:e,bottom:e,left:e}:vc(e)}function bc(e){let{x:t,y:n,width:r,height:i}=e;return{width:r,height:i,top:n,left:t,right:t+r,bottom:n+i,x:t,y:n}}function xc(e,t,n){let{reference:r,floating:i}=e,a=oc(t),o=sc(t),s=ac(o),c=nc(t),l=a===`y`,u=r.x+r.width/2-i.width/2,d=r.y+r.height/2-i.height/2,f=r[s]/2-i[s]/2,p;switch(c){case`top`:p={x:u,y:r.y-i.height};break;case`bottom`:p={x:u,y:r.y+r.height};break;case`right`:p={x:r.x+r.width,y:d};break;case`left`:p={x:r.x-i.width,y:d};break;default:p={x:r.x,y:r.y}}let m=rc(t);return m&&(p[o]+=f*(m===`end`?1:-1)*(n&&l?-1:1)),p}async function Sc(e,t){t===void 0&&(t={});let{x:n,y:r,platform:i,rects:a,elements:o,strategy:s}=e,{boundary:c=`clippingAncestors`,rootBoundary:l=`viewport`,elementContext:u=`floating`,altBoundary:d=!1,padding:f=0}=tc(t,e),p=yc(f),m=o[d?u===`floating`?`reference`:`floating`:u],h=bc(await i.getClippingRect({element:await(i.isElement==null?void 0:i.isElement(m))??!0?m:m.contextElement||await(i.getDocumentElement==null?void 0:i.getDocumentElement(o.floating)),boundary:c,rootBoundary:l,strategy:s})),g=u===`floating`?{x:n,y:r,width:a.floating.width,height:a.floating.height}:a.reference,_=await(i.getOffsetParent==null?void 0:i.getOffsetParent(o.floating)),v=await(i.isElement==null?void 0:i.isElement(_))&&await(i.getScale==null?void 0:i.getScale(_))||{x:1,y:1},y=bc(i.convertOffsetParentRelativeRectToViewportRelativeRect?await i.convertOffsetParentRelativeRectToViewportRelativeRect({elements:o,rect:g,offsetParent:_,strategy:s}):g);return{top:(h.top-y.top+p.top)/v.y,bottom:(y.bottom-h.bottom+p.bottom)/v.y,left:(h.left-y.left+p.left)/v.x,right:(y.right-h.right+p.right)/v.x}}var Cc=50,wc=async(e,t,n)=>{let{placement:r=`bottom`,strategy:i=`absolute`,middleware:a=[],platform:o}=n,s=o.detectOverflow?o:{...o,detectOverflow:Sc},c=await(o.isRTL==null?void 0:o.isRTL(t)),l=await o.getElementRects({reference:e,floating:t,strategy:i}),{x:u,y:d}=xc(l,r,c),f=r,p=0,m={};for(let n=0;n({name:`arrow`,options:e,async fn(t){let{x:n,y:r,placement:i,rects:a,platform:o,elements:s,middlewareData:c}=t,{element:l,padding:u=0}=tc(e,t)||{};if(l==null)return{};let d=yc(u),f={x:n,y:r},p=sc(i),m=ac(p),h=await o.getDimensions(l),g=p===`y`,_=g?`top`:`left`,v=g?`bottom`:`right`,y=g?`clientHeight`:`clientWidth`,b=a.reference[m]+a.reference[p]-f[p]-a.floating[m],x=f[p]-a.reference[p],S=await(o.getOffsetParent==null?void 0:o.getOffsetParent(l)),C=S?S[y]:0;(!C||!await(o.isElement==null?void 0:o.isElement(S)))&&(C=s.floating[y]||a.floating[m]);let w=b/2-x/2,T=C/2-h[m]/2-1,E=Zs(d[_],T),ee=Zs(d[v],T),te=C-h[m]-ee,D=C/2-h[m]/2+w,ne=ec(E,D,te),re=!c.arrow&&rc(i)!=null&&D!==ne&&a.reference[m]/2-(Drc(t)===e),...n.filter(t=>rc(t)!==e)]:n.filter(e=>nc(e)===e)).filter(n=>!e||rc(n)===e||(t?uc(n)!==n:!1))}var Dc=function(e){return e===void 0&&(e={}),{name:`autoPlacement`,options:e,async fn(t){let{rects:n,middlewareData:r,placement:i,platform:a,elements:o}=t,{crossAxis:s=!1,alignment:c,allowedPlacements:l=Xs,autoAlignment:u=!0,...d}=tc(e,t),f=c!==void 0||l===Xs?Ec(c||null,u,l):l,p=r.autoPlacement?.index||0,m=f[p];if(m==null)return{};if(i!==m)return{reset:{placement:f[0]}};let h=await a.detectOverflow(t,d),g=cc(m,n,await(a.isRTL==null?void 0:a.isRTL(o.floating))),_=[h[nc(m)],h[g[0]],h[g[1]]],v=[...r.autoPlacement?.overflows||[],{placement:m,overflows:_}],y=f[p+1];if(y)return{data:{index:p+1,overflows:v},reset:{placement:y}};let b=v.map(e=>{let t=rc(e.placement);return[e.placement,t&&s?e.overflows.slice(0,2).reduce((e,t)=>e+t,0):e.overflows[0],e.overflows]}).sort((e,t)=>e[1]-t[1]),x=b.filter(e=>e[2].slice(0,rc(e[0])?2:3).every(e=>e<=0))[0]?.[0]||b[0][0];return x===i?{}:{data:{index:p+1,overflows:v},reset:{placement:x}}}}},Oc=function(e){return e===void 0&&(e={}),{name:`flip`,options:e,async fn(t){var n;let{placement:r,middlewareData:i,rects:a,initialPlacement:o,platform:s,elements:c}=t,{mainAxis:l=!0,crossAxis:u=!0,fallbackPlacements:d,fallbackStrategy:f=`bestFit`,fallbackAxisSideDirection:p=`none`,flipAlignment:m=!0,...h}=tc(e,t);if((n=i.arrow)!=null&&n.alignmentOffset)return{};let g=nc(r),_=oc(o),v=nc(o)===o,y=await(s.isRTL==null?void 0:s.isRTL(c.floating)),b=d||(v||!m?[_c(o)]:lc(o)),x=p!==`none`;!d&&x&&b.push(...gc(o,m,p,y));let S=[o,...b],C=await s.detectOverflow(t,h),w=[],T=i.flip?.overflows||[];if(l&&w.push(C[g]),u){let e=cc(r,a,y);w.push(C[e[0]],C[e[1]])}if(T=[...T,{placement:r,overflows:w}],!w.every(e=>e<=0)){let e=(i.flip?.index||0)+1,t=S[e];if(t&&(u!==`alignment`||_===oc(t)||T.every(e=>oc(e.placement)!==_||e.overflows[0]>0)))return{data:{index:e,overflows:T},reset:{placement:t}};let n=T.filter(e=>e.overflows[0]<=0).sort((e,t)=>e.overflows[1]-t.overflows[1])[0]?.placement;if(!n)switch(f){case`bestFit`:{let e=T.filter(e=>{if(x){let t=oc(e.placement);return t===_||t===`y`}return!0}).map(e=>[e.placement,e.overflows.filter(e=>e>0).reduce((e,t)=>e+t,0)]).sort((e,t)=>e[1]-t[1])[0]?.[0];e&&(n=e);break}case`initialPlacement`:n=o}if(r!==n)return{reset:{placement:n}}}return{}}}},kc=new Set([`left`,`top`]);async function Ac(e,t){let{placement:n,platform:r,elements:i}=e,a=await(r.isRTL==null?void 0:r.isRTL(i.floating)),o=nc(n),s=rc(n),c=oc(n)===`y`,l=kc.has(o)?-1:1,u=a&&c?-1:1,d=tc(t,e),{mainAxis:f,crossAxis:p,alignmentAxis:m}=typeof d==`number`?{mainAxis:d,crossAxis:0,alignmentAxis:null}:{mainAxis:d.mainAxis||0,crossAxis:d.crossAxis||0,alignmentAxis:d.alignmentAxis};return s&&typeof m==`number`&&(p=s===`end`?m*-1:m),c?{x:p*u,y:f*l}:{x:f*l,y:p*u}}var jc=function(e){return e===void 0&&(e=0),{name:`offset`,options:e,async fn(t){var n;let{x:r,y:i,placement:a,middlewareData:o}=t,s=await Ac(t,e);return a===o.offset?.placement&&(n=o.arrow)!=null&&n.alignmentOffset?{}:{x:r+s.x,y:i+s.y,data:{...s,placement:a}}}}},Mc=function(e){return e===void 0&&(e={}),{name:`shift`,options:e,async fn(t){let{x:n,y:r,placement:i,platform:a}=t,{mainAxis:o=!0,crossAxis:s=!1,limiter:c={fn:e=>{let{x:t,y:n}=e;return{x:t,y:n}}},...l}=tc(e,t),u={x:n,y:r},d=await a.detectOverflow(t,l),f=oc(i),p=ic(f),m=u[p],h=u[f],g=(e,t)=>ec(t+d[e===`y`?`top`:`left`],t,t-d[e===`y`?`bottom`:`right`]);o&&(m=g(p,m)),s&&(h=g(f,h));let _=c.fn({...t,[p]:m,[f]:h});return{..._,data:{x:_.x-n,y:_.y-r,enabled:{[p]:o,[f]:s}}}}}},Nc=function(e){return e===void 0&&(e={}),{name:`size`,options:e,async fn(t){let{placement:n,rects:r,platform:i,elements:a}=t,{apply:o=()=>{},...s}=tc(e,t),c=await i.detectOverflow(t,s),l=nc(n),u=rc(n),d=oc(n)===`y`,{width:f,height:p}=r.floating,m,h;l===`top`||l===`bottom`?(m=l,h=u===(await(i.isRTL==null?void 0:i.isRTL(a.floating))?`start`:`end`)?`left`:`right`):(h=l,m=u===`end`?`top`:`bottom`);let g=p-c.top-c.bottom,_=f-c.left-c.right,v=Zs(p-c[m],g),y=Zs(f-c[h],_),b=t.middlewareData.shift,x=!b,S=v,C=y;b!=null&&b.enabled.x&&(C=_),b!=null&&b.enabled.y&&(S=g),x&&!u&&(d?C=f-2*Qs(c.left,c.right):S=p-2*Qs(c.top,c.bottom)),await o({...t,availableWidth:C,availableHeight:S});let w=await i.getDimensions(a.floating);return f!==w.width||p!==w.height?{reset:{rects:!0}}:{}}}};function Pc(e){return e.ownerDocument?.defaultView||window}function Fc(e){return Pc(e).getComputedStyle(e)}var Ic=Math.min,Lc=Math.max,Rc=Math.round;function zc(e){let t=Fc(e),n=parseFloat(t.width),r=parseFloat(t.height),i=e.offsetWidth,a=e.offsetHeight,o=Rc(n)!==i||Rc(r)!==a;return o&&(n=i,r=a),{width:n,height:r,fallback:o}}function Bc(e){return Gc(e)?(e.nodeName||``).toLowerCase():``}var Vc;function Hc(){if(Vc)return Vc;let e=navigator.userAgentData;return e&&Array.isArray(e.brands)?(Vc=e.brands.map((e=>e.brand+`/`+e.version)).join(` `),Vc):navigator.userAgent}function Uc(e){return e instanceof Pc(e).HTMLElement}function Wc(e){return e instanceof Pc(e).Element}function Gc(e){return e instanceof Pc(e).Node}function Kc(e){return typeof ShadowRoot>`u`?!1:e instanceof Pc(e).ShadowRoot||e instanceof ShadowRoot}function qc(e){let{overflow:t,overflowX:n,overflowY:r,display:i}=Fc(e);return/auto|scroll|overlay|hidden|clip/.test(t+r+n)&&![`inline`,`contents`].includes(i)}function Jc(e){return[`table`,`td`,`th`].includes(Bc(e))}function Yc(e){let t=/firefox/i.test(Hc()),n=Fc(e),r=n.backdropFilter||n.WebkitBackdropFilter;return n.transform!==`none`||n.perspective!==`none`||!!r&&r!==`none`||t&&n.willChange===`filter`||t&&!!n.filter&&n.filter!==`none`||[`transform`,`perspective`].some((e=>n.willChange.includes(e)))||[`paint`,`layout`,`strict`,`content`].some((e=>{let t=n.contain;return t!=null&&t.includes(e)}))}function Xc(){return!/^((?!chrome|android).)*safari/i.test(Hc())}function Zc(e){return[`html`,`body`,`#document`].includes(Bc(e))}function Qc(e){return Wc(e)?e:e.contextElement}var $c={x:1,y:1};function el(e){let t=Qc(e);if(!Uc(t))return $c;let n=t.getBoundingClientRect(),{width:r,height:i,fallback:a}=zc(t),o=(a?Rc(n.width):n.width)/r,s=(a?Rc(n.height):n.height)/i;return o&&Number.isFinite(o)||(o=1),s&&Number.isFinite(s)||(s=1),{x:o,y:s}}function tl(e,t,n,r){t===void 0&&(t=!1),n===void 0&&(n=!1);let i=e.getBoundingClientRect(),a=Qc(e),o=$c;t&&(r?Wc(r)&&(o=el(r)):o=el(e));let s=a?Pc(a):window,c=!Xc()&&n,l=(i.left+(c&&s.visualViewport?.offsetLeft||0))/o.x,u=(i.top+(c&&s.visualViewport?.offsetTop||0))/o.y,d=i.width/o.x,f=i.height/o.y;if(a){let e=Pc(a),t=r&&Wc(r)?Pc(r):r,n=e.frameElement;for(;n&&r&&t!==e;){let e=el(n),t=n.getBoundingClientRect(),r=getComputedStyle(n);t.x+=(n.clientLeft+parseFloat(r.paddingLeft))*e.x,t.y+=(n.clientTop+parseFloat(r.paddingTop))*e.y,l*=e.x,u*=e.y,d*=e.x,f*=e.y,l+=t.x,u+=t.y,n=Pc(n).frameElement}}return{width:d,height:f,top:u,right:l+d,bottom:u+f,left:l,x:l,y:u}}function nl(e){return((Gc(e)?e.ownerDocument:e.document)||window.document).documentElement}function rl(e){return Wc(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function il(e){return tl(nl(e)).left+rl(e).scrollLeft}function al(e){if(Bc(e)===`html`)return e;let t=e.assignedSlot||e.parentNode||Kc(e)&&e.host||nl(e);return Kc(t)?t.host:t}function ol(e){let t=al(e);return Zc(t)?t.ownerDocument.body:Uc(t)&&qc(t)?t:ol(t)}function sl(e,t){t===void 0&&(t=[]);let n=ol(e),r=n===e.ownerDocument?.body,i=Pc(n);return r?t.concat(i,i.visualViewport||[],qc(n)?n:[]):t.concat(n,sl(n))}function cl(e,t,n){return t===`viewport`?bc(function(e,t){let n=Pc(e),r=nl(e),i=n.visualViewport,a=r.clientWidth,o=r.clientHeight,s=0,c=0;if(i){a=i.width,o=i.height;let e=Xc();(e||!e&&t===`fixed`)&&(s=i.offsetLeft,c=i.offsetTop)}return{width:a,height:o,x:s,y:c}}(e,n)):Wc(t)?bc(function(e,t){let n=tl(e,!0,t===`fixed`),r=n.top+e.clientTop,i=n.left+e.clientLeft,a=Uc(e)?el(e):{x:1,y:1};return{width:e.clientWidth*a.x,height:e.clientHeight*a.y,x:i*a.x,y:r*a.y}}(t,n)):bc(function(e){let t=nl(e),n=rl(e),r=e.ownerDocument.body,i=Lc(t.scrollWidth,t.clientWidth,r.scrollWidth,r.clientWidth),a=Lc(t.scrollHeight,t.clientHeight,r.scrollHeight,r.clientHeight),o=-n.scrollLeft+il(e),s=-n.scrollTop;return Fc(r).direction===`rtl`&&(o+=Lc(t.clientWidth,r.clientWidth)-i),{width:i,height:a,x:o,y:s}}(nl(e)))}function ll(e){return Uc(e)&&Fc(e).position!==`fixed`?e.offsetParent:null}function ul(e){let t=Pc(e),n=ll(e);for(;n&&Jc(n)&&Fc(n).position===`static`;)n=ll(n);return n&&(Bc(n)===`html`||Bc(n)===`body`&&Fc(n).position===`static`&&!Yc(n))?t:n||function(e){let t=al(e);for(;Uc(t)&&!Zc(t);){if(Yc(t))return t;t=al(t)}return null}(e)||t}function dl(e,t,n){let r=Uc(t),i=nl(t),a=tl(e,!0,n===`fixed`,t),o={scrollLeft:0,scrollTop:0},s={x:0,y:0};if(r||!r&&n!==`fixed`){if((Bc(t)!==`body`||qc(i))&&(o=rl(t)),Uc(t)){let e=tl(t,!0);s.x=e.x+t.clientLeft,s.y=e.y+t.clientTop}else i&&(s.x=il(i))}return{x:a.left+o.scrollLeft-s.x,y:a.top+o.scrollTop-s.y,width:a.width,height:a.height}}var fl={getClippingRect:function(e){let{element:t,boundary:n,rootBoundary:r,strategy:i}=e,a=[...n===`clippingAncestors`?function(e,t){let n=t.get(e);if(n)return n;let r=sl(e).filter((e=>Wc(e)&&Bc(e)!==`body`)),i=null,a=Fc(e).position===`fixed`,o=a?al(e):e;for(;Wc(o)&&!Zc(o);){let e=Fc(o),t=Yc(o);(a?t||i:t||e.position!==`static`||!i||![`absolute`,`fixed`].includes(i.position))?i=e:r=r.filter((e=>e!==o)),o=al(o)}return t.set(e,r),r}(t,this._c):[].concat(n),r],o=a[0],s=a.reduce(((e,n)=>{let r=cl(t,n,i);return e.top=Lc(r.top,e.top),e.right=Ic(r.right,e.right),e.bottom=Ic(r.bottom,e.bottom),e.left=Lc(r.left,e.left),e}),cl(t,o,i));return{width:s.right-s.left,height:s.bottom-s.top,x:s.left,y:s.top}},convertOffsetParentRelativeRectToViewportRelativeRect:function(e){let{rect:t,offsetParent:n,strategy:r}=e,i=Uc(n),a=nl(n);if(n===a)return t;let o={scrollLeft:0,scrollTop:0},s={x:1,y:1},c={x:0,y:0};if((i||!i&&r!==`fixed`)&&((Bc(n)!==`body`||qc(a))&&(o=rl(n)),Uc(n))){let e=tl(n);s=el(n),c.x=e.x+n.clientLeft,c.y=e.y+n.clientTop}return{width:t.width*s.x,height:t.height*s.y,x:t.x*s.x-o.scrollLeft*s.x+c.x,y:t.y*s.y-o.scrollTop*s.y+c.y}},isElement:Wc,getDimensions:function(e){return Uc(e)?zc(e):e.getBoundingClientRect()},getOffsetParent:ul,getDocumentElement:nl,getScale:el,async getElementRects(e){let{reference:t,floating:n,strategy:r}=e,i=this.getOffsetParent||ul,a=this.getDimensions;return{reference:dl(t,await i(n),r),floating:{x:0,y:0,...await a(n)}}},getClientRects:e=>Array.from(e.getClientRects()),isRTL:e=>Fc(e).direction===`rtl`},pl=(e,t,n)=>{let r=new Map,i={platform:fl,...n},a={...i.platform,_c:r};return wc(e,t,{...i,platform:a})},ml={disabled:!1,distance:5,skidding:0,container:`body`,boundary:void 0,instantMove:!1,disposeTimeout:150,popperTriggers:[],strategy:`absolute`,preventOverflow:!0,flip:!0,shift:!0,overflowPadding:0,arrowPadding:0,arrowOverflow:!0,autoHideOnMousedown:!1,themes:{tooltip:{placement:`top`,triggers:[`hover`,`focus`,`touch`],hideTriggers:e=>[...e,`click`],delay:{show:200,hide:0},handleResize:!1,html:!1,loadingContent:`...`},dropdown:{placement:`bottom`,triggers:[`click`],delay:0,handleResize:!0,autoHide:!0},menu:{$extend:`dropdown`,triggers:[`hover`,`focus`],popperTriggers:[`hover`],delay:{show:0,hide:400}}}};function hl(e,t){let n=ml.themes[e]||{},r;do r=n[t],typeof r>`u`?n.$extend?n=ml.themes[n.$extend]||{}:(n=null,r=ml[t]):n=null;while(n);return r}function gl(e){let t=[e],n=ml.themes[e]||{};do n.$extend&&!n.$resetCss?(t.push(n.$extend),n=ml.themes[n.$extend]||{}):n=null;while(n);return t.map(e=>`v-popper--theme-${e}`)}function _l(e){let t=[e],n=ml.themes[e]||{};do n.$extend?(t.push(n.$extend),n=ml.themes[n.$extend]||{}):n=null;while(n);return t}var vl=!1;if(typeof window<`u`){vl=!1;try{let e=Object.defineProperty({},"passive",{get(){vl=!0}});window.addEventListener(`test`,null,e)}catch{}}var yl=!1;typeof window<`u`&&typeof navigator<`u`&&(yl=/iPad|iPhone|iPod/.test(navigator.userAgent)&&!window.MSStream);var bl=[`auto`,`top`,`bottom`,`left`,`right`].reduce((e,t)=>e.concat([t,`${t}-start`,`${t}-end`]),[]),xl={hover:`mouseenter`,focus:`focus`,click:`click`,touch:`touchstart`,pointer:`pointerdown`},Sl={hover:`mouseleave`,focus:`blur`,click:`click`,touch:`touchend`,pointer:`pointerup`};function Cl(e,t){let n=e.indexOf(t);n!==-1&&e.splice(n,1)}function wl(){return new Promise(e=>requestAnimationFrame(()=>{requestAnimationFrame(e)}))}var Tl=[],El=null,Dl={};function Ol(e){let t=Dl[e];return t||=Dl[e]=[],t}var kl=function(){};typeof window<`u`&&(kl=window.Element);function $(e){return function(t){return hl(t.theme,e)}}var Al=`__floating-vue__popper`,jl=()=>J({name:`VPopper`,provide(){return{[Al]:{parentPopper:this}}},inject:{[Al]:{default:null}},props:{theme:{type:String,required:!0},targetNodes:{type:Function,required:!0},referenceNode:{type:Function,default:null},popperNode:{type:Function,required:!0},shown:{type:Boolean,default:!1},showGroup:{type:String,default:null},ariaId:{default:null},disabled:{type:Boolean,default:$(`disabled`)},positioningDisabled:{type:Boolean,default:$(`positioningDisabled`)},placement:{type:String,default:$(`placement`),validator:e=>bl.includes(e)},delay:{type:[String,Number,Object],default:$(`delay`)},distance:{type:[Number,String],default:$(`distance`)},skidding:{type:[Number,String],default:$(`skidding`)},triggers:{type:Array,default:$(`triggers`)},showTriggers:{type:[Array,Function],default:$(`showTriggers`)},hideTriggers:{type:[Array,Function],default:$(`hideTriggers`)},popperTriggers:{type:Array,default:$(`popperTriggers`)},popperShowTriggers:{type:[Array,Function],default:$(`popperShowTriggers`)},popperHideTriggers:{type:[Array,Function],default:$(`popperHideTriggers`)},container:{type:[String,Object,kl,Boolean],default:$(`container`)},boundary:{type:[String,kl],default:$(`boundary`)},strategy:{type:String,validator:e=>[`absolute`,`fixed`].includes(e),default:$(`strategy`)},autoHide:{type:[Boolean,Function],default:$(`autoHide`)},handleResize:{type:Boolean,default:$(`handleResize`)},instantMove:{type:Boolean,default:$(`instantMove`)},eagerMount:{type:Boolean,default:$(`eagerMount`)},popperClass:{type:[String,Array,Object],default:$(`popperClass`)},computeTransformOrigin:{type:Boolean,default:$(`computeTransformOrigin`)},autoMinSize:{type:Boolean,default:$(`autoMinSize`)},autoSize:{type:[Boolean,String],default:$(`autoSize`)},autoMaxSize:{type:Boolean,default:$(`autoMaxSize`)},autoBoundaryMaxSize:{type:Boolean,default:$(`autoBoundaryMaxSize`)},preventOverflow:{type:Boolean,default:$(`preventOverflow`)},overflowPadding:{type:[Number,String],default:$(`overflowPadding`)},arrowPadding:{type:[Number,String],default:$(`arrowPadding`)},arrowOverflow:{type:Boolean,default:$(`arrowOverflow`)},flip:{type:Boolean,default:$(`flip`)},shift:{type:Boolean,default:$(`shift`)},shiftCrossAxis:{type:Boolean,default:$(`shiftCrossAxis`)},noAutoFocus:{type:Boolean,default:$(`noAutoFocus`)},disposeTimeout:{type:Number,default:$(`disposeTimeout`)}},emits:{show:()=>!0,hide:()=>!0,"update:shown":e=>!0,"apply-show":()=>!0,"apply-hide":()=>!0,"close-group":()=>!0,"close-directive":()=>!0,"auto-hide":()=>!0,resize:()=>!0},data(){return{isShown:!1,isMounted:!1,skipTransition:!1,classes:{showFrom:!1,showTo:!1,hideFrom:!1,hideTo:!0},result:{x:0,y:0,placement:``,strategy:this.strategy,arrow:{x:0,y:0,centerOffset:0},transformOrigin:null},randomId:`popper_${[Math.random(),Date.now()].map(e=>e.toString(36).substring(2,10)).join(`_`)}`,shownChildren:new Set,lastAutoHide:!0,pendingHide:!1,containsGlobalTarget:!1,isDisposed:!0,mouseDownContains:!1}},computed:{popperId(){return this.ariaId==null?this.randomId:this.ariaId},shouldMountContent(){return this.eagerMount||this.isMounted},slotData(){return{popperId:this.popperId,isShown:this.isShown,shouldMountContent:this.shouldMountContent,skipTransition:this.skipTransition,autoHide:typeof this.autoHide==`function`?this.lastAutoHide:this.autoHide,show:this.show,hide:this.hide,handleResize:this.handleResize,onResize:this.onResize,classes:{...this.classes,popperClass:this.popperClass},result:this.positioningDisabled?null:this.result,attrs:this.$attrs}},parentPopper(){return this[Al]?.parentPopper},hasPopperShowTriggerHover(){return this.popperTriggers?.includes(`hover`)||this.popperShowTriggers?.includes(`hover`)}},watch:{shown:`$_autoShowHide`,disabled(e){e?this.dispose():this.init()},async container(){this.isShown&&(this.$_ensureTeleport(),await this.$_computePosition())},triggers:{handler:`$_refreshListeners`,deep:!0},positioningDisabled:`$_refreshListeners`,...[`placement`,`distance`,`skidding`,`boundary`,`strategy`,`overflowPadding`,`arrowPadding`,`preventOverflow`,`shift`,`shiftCrossAxis`,`flip`].reduce((e,t)=>(e[t]=`$_computePosition`,e),{})},created(){this.autoMinSize&&console.warn('[floating-vue] `autoMinSize` option is deprecated. Use `autoSize="min"` instead.'),this.autoMaxSize&&console.warn("[floating-vue] `autoMaxSize` option is deprecated. Use `autoBoundaryMaxSize` instead.")},mounted(){this.init(),this.$_detachPopperNode()},activated(){this.$_autoShowHide()},deactivated(){this.hide()},beforeUnmount(){this.dispose()},methods:{show({event:e=null,skipDelay:t=!1,force:n=!1}={}){var r;(r=this.parentPopper)!=null&&r.lockedChild&&this.parentPopper.lockedChild!==this||(this.pendingHide=!1,(n||!this.disabled)&&(this.parentPopper?.lockedChild===this&&(this.parentPopper.lockedChild=null),this.$_scheduleShow(e,t),this.$emit(`show`),this.$_showFrameLocked=!0,requestAnimationFrame(()=>{this.$_showFrameLocked=!1})),this.$emit(`update:shown`,!0))},hide({event:e=null,skipDelay:t=!1}={}){if(!this.$_hideInProgress){if(this.shownChildren.size>0){this.pendingHide=!0;return}if(this.hasPopperShowTriggerHover&&this.$_isAimingPopper()){this.parentPopper&&(this.parentPopper.lockedChild=this,clearTimeout(this.parentPopper.lockedChildTimer),this.parentPopper.lockedChildTimer=setTimeout(()=>{this.parentPopper.lockedChild===this&&(this.parentPopper.lockedChild.hide({skipDelay:t}),this.parentPopper.lockedChild=null)},1e3));return}this.parentPopper?.lockedChild===this&&(this.parentPopper.lockedChild=null),this.pendingHide=!1,this.$_scheduleHide(e,t),this.$emit(`hide`),this.$emit(`update:shown`,!1)}},init(){this.isDisposed&&(this.isDisposed=!1,this.isMounted=!1,this.$_events=[],this.$_preventShow=!1,this.$_referenceNode=this.referenceNode?.call(this)??this.$el,this.$_targetNodes=this.targetNodes().filter(e=>e.nodeType===e.ELEMENT_NODE),this.$_popperNode=this.popperNode(),this.$_innerNode=this.$_popperNode.querySelector(`.v-popper__inner`),this.$_arrowNode=this.$_popperNode.querySelector(`.v-popper__arrow-container`),this.$_swapTargetAttrs(`title`,`data-original-title`),this.$_detachPopperNode(),this.triggers.length&&this.$_addEventListeners(),this.shown&&this.show())},dispose(){this.isDisposed||(this.isDisposed=!0,this.$_removeEventListeners(),this.hide({skipDelay:!0}),this.$_detachPopperNode(),this.isMounted=!1,this.isShown=!1,this.$_updateParentShownChildren(!1),this.$_swapTargetAttrs(`data-original-title`,`title`))},async onResize(){this.isShown&&(await this.$_computePosition(),this.$emit(`resize`))},async $_computePosition(){if(this.isDisposed||this.positioningDisabled)return;let e={strategy:this.strategy,middleware:[]};(this.distance||this.skidding)&&e.middleware.push(jc({mainAxis:this.distance,crossAxis:this.skidding}));let t=this.placement.startsWith(`auto`);if(t?e.middleware.push(Dc({alignment:this.placement.split(`-`)[1]??``})):e.placement=this.placement,this.preventOverflow&&(this.shift&&e.middleware.push(Mc({padding:this.overflowPadding,boundary:this.boundary,crossAxis:this.shiftCrossAxis})),!t&&this.flip&&e.middleware.push(Oc({padding:this.overflowPadding,boundary:this.boundary}))),e.middleware.push(Tc({element:this.$_arrowNode,padding:this.arrowPadding})),this.arrowOverflow&&e.middleware.push({name:`arrowOverflow`,fn:({placement:e,rects:t,middlewareData:n})=>{let r,{centerOffset:i}=n.arrow;return r=e.startsWith(`top`)||e.startsWith(`bottom`)?Math.abs(i)>t.reference.width/2:Math.abs(i)>t.reference.height/2,{data:{overflow:r}}}}),this.autoMinSize||this.autoSize){let t=this.autoSize?this.autoSize:this.autoMinSize?`min`:null;e.middleware.push({name:`autoSize`,fn:({rects:e,placement:n,middlewareData:r})=>{var i;if((i=r.autoSize)!=null&&i.skip)return{};let a,o;return n.startsWith(`top`)||n.startsWith(`bottom`)?a=e.reference.width:o=e.reference.height,this.$_innerNode.style[t===`min`?`minWidth`:t===`max`?`maxWidth`:`width`]=a==null?null:`${a}px`,this.$_innerNode.style[t===`min`?`minHeight`:t===`max`?`maxHeight`:`height`]=o==null?null:`${o}px`,{data:{skip:!0},reset:{rects:!0}}}})}(this.autoMaxSize||this.autoBoundaryMaxSize)&&(this.$_innerNode.style.maxWidth=null,this.$_innerNode.style.maxHeight=null,e.middleware.push(Nc({boundary:this.boundary,padding:this.overflowPadding,apply:({availableWidth:e,availableHeight:t})=>{this.$_innerNode.style.maxWidth=e==null?null:`${e}px`,this.$_innerNode.style.maxHeight=t==null?null:`${t}px`}})));let n=await pl(this.$_referenceNode,this.$_popperNode,e);Object.assign(this.result,{x:n.x,y:n.y,placement:n.placement,strategy:n.strategy,arrow:{...n.middlewareData.arrow,...n.middlewareData.arrowOverflow}})},$_scheduleShow(e,t=!1){if(this.$_updateParentShownChildren(!0),this.$_hideInProgress=!1,clearTimeout(this.$_scheduleTimer),El&&this.instantMove&&El.instantMove&&El!==this.parentPopper){El.$_applyHide(!0),this.$_applyShow(!0);return}t?this.$_applyShow():this.$_scheduleTimer=setTimeout(this.$_applyShow.bind(this),this.$_computeDelay(`show`))},$_scheduleHide(e,t=!1){if(this.shownChildren.size>0){this.pendingHide=!0;return}this.$_updateParentShownChildren(!1),this.$_hideInProgress=!0,clearTimeout(this.$_scheduleTimer),this.isShown&&(El=this),t?this.$_applyHide():this.$_scheduleTimer=setTimeout(this.$_applyHide.bind(this),this.$_computeDelay(`hide`))},$_computeDelay(e){let t=this.delay;return parseInt(t&&t[e]||t||0)},async $_applyShow(e=!1){clearTimeout(this.$_disposeTimer),clearTimeout(this.$_scheduleTimer),this.skipTransition=e,!this.isShown&&(this.$_ensureTeleport(),await wl(),await this.$_computePosition(),await this.$_applyShowEffect(),this.positioningDisabled||this.$_registerEventListeners([...sl(this.$_referenceNode),...sl(this.$_popperNode)],`scroll`,()=>{this.$_computePosition()}))},async $_applyShowEffect(){if(this.$_hideInProgress)return;if(this.computeTransformOrigin){let e=this.$_referenceNode.getBoundingClientRect(),t=this.$_popperNode.querySelector(`.v-popper__wrapper`),n=t.parentNode.getBoundingClientRect(),r=e.x+e.width/2-(n.left+t.offsetLeft),i=e.y+e.height/2-(n.top+t.offsetTop);this.result.transformOrigin=`${r}px ${i}px`}this.isShown=!0,this.$_applyAttrsToTarget({"aria-describedby":this.popperId,"data-popper-shown":``});let e=this.showGroup;if(e){let t;for(let n=0;n0){this.pendingHide=!0,this.$_hideInProgress=!1;return}if(clearTimeout(this.$_scheduleTimer),!this.isShown)return;this.skipTransition=e,Cl(Tl,this),Tl.length===0&&document.body.classList.remove(`v-popper--some-open`);for(let e of _l(this.theme)){let t=Ol(e);Cl(t,this),t.length===0&&document.body.classList.remove(`v-popper--some-open--${e}`)}El===this&&(El=null),this.isShown=!1,this.$_applyAttrsToTarget({"aria-describedby":void 0,"data-popper-shown":void 0}),clearTimeout(this.$_disposeTimer);let t=this.disposeTimeout;t!==null&&(this.$_disposeTimer=setTimeout(()=>{this.$_popperNode&&(this.$_detachPopperNode(),this.isMounted=!1)},t)),this.$_removeEventListeners(`scroll`),this.$emit(`apply-hide`),this.classes.showFrom=!1,this.classes.showTo=!1,this.classes.hideFrom=!0,this.classes.hideTo=!1,await wl(),this.classes.hideFrom=!1,this.classes.hideTo=!0},$_autoShowHide(){this.shown?this.show():this.hide()},$_ensureTeleport(){if(this.isDisposed)return;let e=this.container;if(typeof e==`string`?e=window.document.querySelector(e):e===!1&&(e=this.$_targetNodes[0].parentNode),!e)throw Error(`No container for popover: `+this.container);e.appendChild(this.$_popperNode),this.isMounted=!0},$_addEventListeners(){let e=e=>{this.isShown&&!this.$_hideInProgress||(e.usedByTooltip=!0,!this.$_preventShow&&this.show({event:e}))};this.$_registerTriggerListeners(this.$_targetNodes,xl,this.triggers,this.showTriggers,e),this.$_registerTriggerListeners([this.$_popperNode],xl,this.popperTriggers,this.popperShowTriggers,e);let t=e=>{e.usedByTooltip||this.hide({event:e})};this.$_registerTriggerListeners(this.$_targetNodes,Sl,this.triggers,this.hideTriggers,t),this.$_registerTriggerListeners([this.$_popperNode],Sl,this.popperTriggers,this.popperHideTriggers,t)},$_registerEventListeners(e,t,n){this.$_events.push({targetNodes:e,eventType:t,handler:n}),e.forEach(e=>e.addEventListener(t,n,vl?{passive:!0}:void 0))},$_registerTriggerListeners(e,t,n,r,i){let a=n;r!=null&&(a=typeof r==`function`?r(a):r),a.forEach(n=>{let r=t[n];r&&this.$_registerEventListeners(e,r,i)})},$_removeEventListeners(e){let t=[];this.$_events.forEach(n=>{let{targetNodes:r,eventType:i,handler:a}=n;!e||e===i?r.forEach(e=>e.removeEventListener(i,a)):t.push(n)}),this.$_events=t},$_refreshListeners(){this.isDisposed||(this.$_removeEventListeners(),this.$_addEventListeners())},$_handleGlobalClose(e,t=!1){this.$_showFrameLocked||(this.hide({event:e}),e.closePopover?this.$emit(`close-directive`):this.$emit(`auto-hide`),t&&(this.$_preventShow=!0,setTimeout(()=>{this.$_preventShow=!1},300)))},$_detachPopperNode(){this.$_popperNode.parentNode&&this.$_popperNode.parentNode.removeChild(this.$_popperNode)},$_swapTargetAttrs(e,t){for(let n of this.$_targetNodes){let r=n.getAttribute(e);r&&(n.removeAttribute(e),n.setAttribute(t,r))}},$_applyAttrsToTarget(e){for(let t of this.$_targetNodes)for(let n in e){let r=e[n];r==null?t.removeAttribute(n):t.setAttribute(n,r)}},$_updateParentShownChildren(e){let t=this.parentPopper;for(;t;)e?t.shownChildren.add(this.randomId):(t.shownChildren.delete(this.randomId),t.pendingHide&&t.hide()),t=t.parentPopper},$_isAimingPopper(){let e=this.$_referenceNode.getBoundingClientRect();if(Bl>=e.left&&Bl<=e.right&&Vl>=e.top&&Vl<=e.bottom){let e=this.$_popperNode.getBoundingClientRect(),t=Bl-Rl,n=Vl-zl,r=e.left+e.width/2-Rl+(e.top+e.height/2)-zl+e.width+e.height,i=Rl+t*r,a=zl+n*r;return Hl(Rl,zl,i,a,e.left,e.top,e.left,e.bottom)||Hl(Rl,zl,i,a,e.left,e.top,e.right,e.top)||Hl(Rl,zl,i,a,e.right,e.top,e.right,e.bottom)||Hl(Rl,zl,i,a,e.left,e.bottom,e.right,e.bottom)}return!1}},render(){return this.$slots.default(this.slotData)}});if(typeof document<`u`&&typeof window<`u`){if(yl){let e=!vl||{passive:!0,capture:!0};document.addEventListener(`touchstart`,e=>Ml(e,!0),e),document.addEventListener(`touchend`,e=>Nl(e,!0),e)}else window.addEventListener(`mousedown`,e=>Ml(e,!1),!0),window.addEventListener(`click`,e=>Nl(e,!1),!0);window.addEventListener(`resize`,Ll)}function Ml(e,t){if(ml.autoHideOnMousedown)Pl(e,t);else for(let t=0;t=0;r--){let i=Tl[r];try{let r=i.containsGlobalTarget=i.mouseDownContains||i.popperNode().contains(e.target);i.pendingHide=!1,requestAnimationFrame(()=>{if(i.pendingHide=!1,!n[i.randomId]&&Fl(i,r,e)){if(i.$_handleGlobalClose(e,t),!e.closeAllPopover&&e.closePopover&&r){let e=i.parentPopper;for(;e;)n[e.randomId]=!0,e=e.parentPopper;return}let a=i.parentPopper;for(;a&&Fl(a,a.containsGlobalTarget,e);)a.$_handleGlobalClose(e,t),a=a.parentPopper}})}catch{}}}function Fl(e,t,n){return n.closeAllPopover||n.closePopover&&t||Il(e,n)&&!t}function Il(e,t){if(typeof e.autoHide==`function`){let n=e.autoHide(t);return e.lastAutoHide=n,n}return e.autoHide}function Ll(){for(let e=0;e{Rl=Bl,zl=Vl,Bl=e.clientX,Vl=e.clientY},vl?{passive:!0}:void 0);function Hl(e,t,n,r,i,a,o,s){let c=((o-i)*(t-a)-(s-a)*(e-i))/((s-a)*(n-e)-(o-i)*(r-t)),l=((n-e)*(t-a)-(r-t)*(e-i))/((s-a)*(n-e)-(o-i)*(r-t));return c>=0&&c<=1&&l>=0&&l<=1}var Ul={extends:jl()},Wl=(e,t)=>{let n=e.__vccOpts||e;for(let[e,r]of t)n[e]=r;return n};function Gl(e,t,n,r,i,a){return G(),j(`div`,{ref:`reference`,class:V([`v-popper`,{"v-popper--shown":e.slotData.isShown}])},[Pe(e.$slots,`default`,Ue(_e(e.slotData)))],2)}var Kl=Wl(Ul,[[`render`,Gl]]);function ql(){var e=window.navigator.userAgent,t=e.indexOf(`MSIE `);if(t>0)return parseInt(e.substring(t+5,e.indexOf(`.`,t)),10);if(e.indexOf(`Trident/`)>0){var n=e.indexOf(`rv:`);return parseInt(e.substring(n+3,e.indexOf(`.`,n)),10)}var r=e.indexOf(`Edge/`);return r>0?parseInt(e.substring(r+5,e.indexOf(`.`,r)),10):-1}var Jl;function Yl(){Yl.init||(Yl.init=!0,Jl=ql()!==-1)}var Xl={name:`ResizeObserver`,props:{emitOnMount:{type:Boolean,default:!1},ignoreWidth:{type:Boolean,default:!1},ignoreHeight:{type:Boolean,default:!1}},emits:[`notify`],mounted(){Yl(),Ae(()=>{this._w=this.$el.offsetWidth,this._h=this.$el.offsetHeight,this.emitOnMount&&this.emitSize()});let e=document.createElement(`object`);this._resizeObject=e,e.setAttribute(`aria-hidden`,`true`),e.setAttribute(`tabindex`,-1),e.onload=this.addResizeHandlers,e.type=`text/html`,Jl&&this.$el.appendChild(e),e.data=`about:blank`,Jl||this.$el.appendChild(e)},beforeUnmount(){this.removeResizeHandlers()},methods:{compareAndNotify(){(!this.ignoreWidth&&this._w!==this.$el.offsetWidth||!this.ignoreHeight&&this._h!==this.$el.offsetHeight)&&(this._w=this.$el.offsetWidth,this._h=this.$el.offsetHeight,this.emitSize())},emitSize(){this.$emit(`notify`,{width:this._w,height:this._h})},addResizeHandlers(){this._resizeObject.contentDocument.defaultView.addEventListener(`resize`,this.compareAndNotify),this.compareAndNotify()},removeResizeHandlers(){this._resizeObject&&this._resizeObject.onload&&(!Jl&&this._resizeObject.contentDocument&&this._resizeObject.contentDocument.defaultView.removeEventListener(`resize`,this.compareAndNotify),this.$el.removeChild(this._resizeObject),this._resizeObject.onload=null,this._resizeObject=null)}}},Zl=et(`data-v-b329ee4c`);Ne(`data-v-b329ee4c`);var Ql={class:`resize-observer`,tabindex:`-1`};Ce(),Xl.render=Zl((e,t,n,r,i,a)=>(G(),F(`div`,Ql))),Xl.__scopeId=`data-v-b329ee4c`,Xl.__file=`src/components/ResizeObserver.vue`;var $l=(e=`theme`)=>({computed:{themeClass(){return gl(this[e])}}}),eu=J({name:`VPopperContent`,components:{ResizeObserver:Xl},mixins:[$l()],props:{popperId:String,theme:String,shown:Boolean,mounted:Boolean,skipTransition:Boolean,autoHide:Boolean,handleResize:Boolean,classes:Object,result:Object},emits:[`hide`,`resize`],methods:{toPx(e){return e!=null&&!isNaN(e)?`${e}px`:null}}}),tu=[`id`,`aria-hidden`,`tabindex`,`data-popper-placement`],nu={ref:`inner`,class:`v-popper__inner`},ru=[P(`div`,{class:`v-popper__arrow-outer`},null,-1),P(`div`,{class:`v-popper__arrow-inner`},null,-1)];function iu(e,t,n,r,i,a){let o=De(`ResizeObserver`);return G(),j(`div`,{id:e.popperId,ref:`popover`,class:V([`v-popper__popper`,[e.themeClass,e.classes.popperClass,{"v-popper__popper--shown":e.shown,"v-popper__popper--hidden":!e.shown,"v-popper__popper--show-from":e.classes.showFrom,"v-popper__popper--show-to":e.classes.showTo,"v-popper__popper--hide-from":e.classes.hideFrom,"v-popper__popper--hide-to":e.classes.hideTo,"v-popper__popper--skip-transition":e.skipTransition,"v-popper__popper--arrow-overflow":e.result&&e.result.arrow.overflow,"v-popper__popper--no-positioning":!e.result}]]),style:R(e.result?{position:e.result.strategy,transform:`translate3d(${Math.round(e.result.x)}px,${Math.round(e.result.y)}px,0)`}:void 0),"aria-hidden":e.shown?`false`:`true`,tabindex:e.autoHide?0:void 0,"data-popper-placement":e.result?e.result.placement:void 0,onKeyup:t[2]||=ye(t=>e.autoHide&&e.$emit(`hide`),[`esc`])},[P(`div`,{class:`v-popper__backdrop`,onClick:t[0]||=t=>e.autoHide&&e.$emit(`hide`)}),P(`div`,{class:`v-popper__wrapper`,style:R(e.result?{transformOrigin:e.result.transformOrigin}:void 0)},[P(`div`,nu,[e.mounted?(G(),j(H,{key:0},[P(`div`,null,[Pe(e.$slots,`default`)]),e.handleResize?(G(),F(o,{key:0,onNotify:t[1]||=t=>e.$emit(`resize`,t)})):A(``,!0)],64)):A(``,!0)],512),P(`div`,{ref:`arrow`,class:`v-popper__arrow-container`,style:R(e.result?{left:e.toPx(e.result.arrow.x),top:e.toPx(e.result.arrow.y)}:void 0)},ru,4)],4)],46,tu)}var au=Wl(eu,[[`render`,iu]]),ou={methods:{show(...e){return this.$refs.popper.show(...e)},hide(...e){return this.$refs.popper.hide(...e)},dispose(...e){return this.$refs.popper.dispose(...e)},onResize(...e){return this.$refs.popper.onResize(...e)}}},su=function(){};typeof window<`u`&&(su=window.Element);var cu=J({name:`VPopperWrapper`,components:{Popper:Kl,PopperContent:au},mixins:[ou,$l(`finalTheme`)],props:{theme:{type:String,default:null},referenceNode:{type:Function,default:null},shown:{type:Boolean,default:!1},showGroup:{type:String,default:null},ariaId:{default:null},disabled:{type:Boolean,default:void 0},positioningDisabled:{type:Boolean,default:void 0},placement:{type:String,default:void 0},delay:{type:[String,Number,Object],default:void 0},distance:{type:[Number,String],default:void 0},skidding:{type:[Number,String],default:void 0},triggers:{type:Array,default:void 0},showTriggers:{type:[Array,Function],default:void 0},hideTriggers:{type:[Array,Function],default:void 0},popperTriggers:{type:Array,default:void 0},popperShowTriggers:{type:[Array,Function],default:void 0},popperHideTriggers:{type:[Array,Function],default:void 0},container:{type:[String,Object,su,Boolean],default:void 0},boundary:{type:[String,su],default:void 0},strategy:{type:String,default:void 0},autoHide:{type:[Boolean,Function],default:void 0},handleResize:{type:Boolean,default:void 0},instantMove:{type:Boolean,default:void 0},eagerMount:{type:Boolean,default:void 0},popperClass:{type:[String,Array,Object],default:void 0},computeTransformOrigin:{type:Boolean,default:void 0},autoMinSize:{type:Boolean,default:void 0},autoSize:{type:[Boolean,String],default:void 0},autoMaxSize:{type:Boolean,default:void 0},autoBoundaryMaxSize:{type:Boolean,default:void 0},preventOverflow:{type:Boolean,default:void 0},overflowPadding:{type:[Number,String],default:void 0},arrowPadding:{type:[Number,String],default:void 0},arrowOverflow:{type:Boolean,default:void 0},flip:{type:Boolean,default:void 0},shift:{type:Boolean,default:void 0},shiftCrossAxis:{type:Boolean,default:void 0},noAutoFocus:{type:Boolean,default:void 0},disposeTimeout:{type:Number,default:void 0}},emits:{show:()=>!0,hide:()=>!0,"update:shown":e=>!0,"apply-show":()=>!0,"apply-hide":()=>!0,"close-group":()=>!0,"close-directive":()=>!0,"auto-hide":()=>!0,resize:()=>!0},computed:{finalTheme(){return this.theme??this.$options.vPopperTheme}},methods:{getTargetNodes(){return Array.from(this.$el.children).filter(e=>e!==this.$refs.popperContent.$el)}}});function lu(e,t,n,r,i,a){let o=De(`PopperContent`),s=De(`Popper`);return G(),F(s,Se({ref:`popper`},e.$props,{theme:e.finalTheme,"target-nodes":e.getTargetNodes,"popper-node":()=>e.$refs.popperContent.$el,class:[e.themeClass],onShow:t[0]||=()=>e.$emit(`show`),onHide:t[1]||=()=>e.$emit(`hide`),"onUpdate:shown":t[2]||=t=>e.$emit(`update:shown`,t),onApplyShow:t[3]||=()=>e.$emit(`apply-show`),onApplyHide:t[4]||=()=>e.$emit(`apply-hide`),onCloseGroup:t[5]||=()=>e.$emit(`close-group`),onCloseDirective:t[6]||=()=>e.$emit(`close-directive`),onAutoHide:t[7]||=()=>e.$emit(`auto-hide`),onResize:t[8]||=()=>e.$emit(`resize`)}),{default:z(({popperId:t,isShown:n,shouldMountContent:r,skipTransition:i,autoHide:a,show:s,hide:c,handleResize:l,onResize:u,classes:d,result:f})=>[Pe(e.$slots,`default`,{shown:n,show:s,hide:c}),I(o,{ref:`popperContent`,"popper-id":t,theme:e.finalTheme,shown:n,mounted:r,"skip-transition":i,"auto-hide":a,"handle-resize":l,classes:d,result:f,onHide:c,onResize:u},{default:z(()=>[Pe(e.$slots,`popper`,{shown:n,hide:c})]),_:2},1032,[`popper-id`,`theme`,`shown`,`mounted`,`skip-transition`,`auto-hide`,`handle-resize`,`classes`,`result`,`onHide`,`onResize`])]),_:3},16,[`theme`,`target-nodes`,`popper-node`,`class`])}var uu=Wl(cu,[[`render`,lu]]);({...uu}),{...uu},{...uu};var du=J({name:`VTooltipDirective`,components:{Popper:jl(),PopperContent:au},mixins:[ou],inheritAttrs:!1,props:{theme:{type:String,default:`tooltip`},html:{type:Boolean,default:e=>hl(e.theme,`html`)},content:{type:[String,Number,Function],default:null},loadingContent:{type:String,default:e=>hl(e.theme,`loadingContent`)},targetNodes:{type:Function,required:!0}},data(){return{asyncContent:null}},computed:{isContentAsync(){return typeof this.content==`function`},loading(){return this.isContentAsync&&this.asyncContent==null},finalContent(){return this.isContentAsync?this.loading?this.loadingContent:this.asyncContent:this.content}},watch:{content:{handler(){this.fetchContent(!0)},immediate:!0},async finalContent(){await this.$nextTick(),this.$refs.popper.onResize()}},created(){this.$_fetchId=0},methods:{fetchContent(e){if(typeof this.content==`function`&&this.$_isShown&&(e||!this.$_loading&&this.asyncContent==null)){this.asyncContent=null,this.$_loading=!0;let e=++this.$_fetchId,t=this.content(this);t.then?t.then(t=>this.onResult(e,t)):this.onResult(e,t)}},onResult(e,t){e===this.$_fetchId&&(this.$_loading=!1,this.asyncContent=t)},onShow(){this.$_isShown=!0,this.fetchContent()},onHide(){this.$_isShown=!1}}}),fu=[`innerHTML`],pu=[`textContent`];function mu(e,t,n,r,i,a){let o=De(`PopperContent`),s=De(`Popper`);return G(),F(s,Se({ref:`popper`},e.$attrs,{theme:e.theme,"target-nodes":e.targetNodes,"popper-node":()=>e.$refs.popperContent.$el,onApplyShow:e.onShow,onApplyHide:e.onHide}),{default:z(({popperId:t,isShown:n,shouldMountContent:r,skipTransition:i,autoHide:a,hide:s,handleResize:c,onResize:l,classes:u,result:d})=>[I(o,{ref:`popperContent`,class:V({"v-popper--tooltip-loading":e.loading}),"popper-id":t,theme:e.theme,shown:n,mounted:r,"skip-transition":i,"auto-hide":a,"handle-resize":c,classes:u,result:d,onHide:s,onResize:l},{default:z(()=>[e.html?(G(),j(`div`,{key:0,innerHTML:e.finalContent},null,8,fu)):(G(),j(`div`,{key:1,textContent:q(e.finalContent)},null,8,pu))]),_:2},1032,[`class`,`popper-id`,`theme`,`shown`,`mounted`,`skip-transition`,`auto-hide`,`handle-resize`,`classes`,`result`,`onHide`,`onResize`])]),_:1},16,[`theme`,`target-nodes`,`popper-node`,`onApplyShow`,`onApplyHide`])}var hu=Wl(du,[[`render`,mu]]),gu=`v-popper--has-tooltip`;function _u(e,t){let n=e.placement;if(!n&&t)for(let e of bl)t[e]&&(n=e);return n||=hl(e.theme||`tooltip`,`placement`),n}function vu(e,t,n){let r,i=typeof t;return r=i===`string`?{content:t}:t&&i===`object`?t:{content:!1},r.placement=_u(r,n),r.targetNodes=()=>[e],r.referenceNode=()=>e,r}var yu,bu,xu=0;function Su(){if(yu)return;bu=B([]),yu=Oe({name:`VTooltipDirectiveApp`,setup(){return{directives:bu}},render(){return this.directives.map(e=>je(hu,{...e.options,shown:e.shown||e.options.shown,key:e.id}))},devtools:{hide:!0}});let e=document.createElement(`div`);document.body.appendChild(e),yu.mount(e)}function Cu(e,t,n){Su();let r=B(vu(e,t,n)),i=B(!1),a={id:xu++,options:r,shown:i};return bu.value.push(a),e.classList&&e.classList.add(gu),e.$_popper={options:r,item:a,show(){i.value=!0},hide(){i.value=!1}}}function wu(e){if(e.$_popper){let t=bu.value.indexOf(e.$_popper.item);t!==-1&&bu.value.splice(t,1),delete e.$_popper,delete e.$_popperOldShown,delete e.$_popperMountTarget}e.classList&&e.classList.remove(gu)}function Tu(e,{value:t,modifiers:n}){let r=vu(e,t,n);if(!r.content||hl(r.theme||`tooltip`,`disabled`))wu(e);else{let i;e.$_popper?(i=e.$_popper,i.options.value=r):i=Cu(e,t,n),typeof t.shown<`u`&&t.shown!==e.$_popperOldShown&&(e.$_popperOldShown=t.shown,t.shown?i.show():i.hide())}}var Eu={beforeMount:Tu,updated:Tu,beforeUnmount(e){wu(e)}},Du=J({__name:`ActionIconButton`,props:{as:{},to:{},href:{},icon:{},tooltip:{},active:{type:Boolean},disabled:{type:Boolean},label:{},compact:{type:Boolean},activeClass:{}},setup(e){let t=e,n=N(()=>t.as?t.as:t.href!=null||t.to!=null?`a`:`button`),r=N(()=>n.value===`a`),i=N(()=>n.value===`button`),a=N(()=>t.compact?`btn-icon-compact`:`btn-icon`),o=N(()=>t.compact?`w-auto! h-auto! p-[0.25em]`:`w-auto! h-auto! p-[0.5em]`),s=N(()=>t.active?t.activeClass||`color-active bg-active op100`:``);return(t,c)=>Ze((G(),F(he(n.value),{type:i.value?`button`:void 0,class:V([a.value,o.value,s.value,{"pointer-events-none op-mute":e.disabled&&!i.value}]),href:r.value?e.href??e.to:void 0,to:e.as&&e.to!=null?e.to:void 0,disabled:i.value?e.disabled:void 0,"aria-disabled":e.disabled&&!i.value||void 0,"aria-label":e.label??e.tooltip,"aria-pressed":e.active||void 0},{default:z(()=>[e.icon?(G(),j(`span`,{key:0,class:V(e.icon),"aria-hidden":`true`},null,2)):A(``,!0),Pe(t.$slots,`default`),Pe(t.$slots,`badge`)]),_:3},8,[`type`,`class`,`href`,`to`,`disabled`,`aria-disabled`,`aria-label`,`aria-pressed`])),[[U(Eu),e.tooltip]])}}),Ou={class:`devframes-view-assets-error absolute inset-0 flex items-center justify-center overflow-auto bg-base p6`},ku={class:`w-full max-w-100 flex flex-col items-center gap-2 text-center`},Au={class:`text-xs color-faint`},ju={class:`font-mono`},Mu={class:`mt-2 w-full flex items-center gap-1 border border-base rounded bg-secondary py1 pl2.5 pr1`},Nu={class:`flex-1 truncate text-left text-xs font-mono color-base`},Pu={class:`w-full max-h-24 overflow-auto text-left text-xs font-mono color-faint break-words`},Fu=J({__name:`ViewAssetsError`,props:{error:{}},emits:[`retry`],setup(e,{emit:t}){let n=e,r=t,i=N(()=>`npm install ${n.error.package}@${n.error.version}`),a=B(!1),o;async function s(){try{await navigator.clipboard.writeText(i.value),a.value=!0,clearTimeout(o),o=setTimeout(()=>a.value=!1,1500)}catch{}}return(t,n)=>(G(),j(`div`,Ou,[P(`div`,ku,[n[5]||=P(`div`,{class:`i-ph:cloud-warning-duotone text-3xl color-faint`},null,-1),n[6]||=P(`div`,{class:`text-sm color-muted`},` Failed to load assets remotely `,-1),P(`div`,Au,[n[2]||=M(` This tool's UI is published as `,-1),P(`span`,ju,q(e.error.package)+`@`+q(e.error.version),1),n[3]||=M(`. Install it locally, or enable network access to continue. `,-1)]),P(`div`,Mu,[P(`code`,Nu,q(i.value),1),I(Du,{class:`text-sm`,icon:a.value?`i-ph:check`:`i-ph:copy`,label:a.value?`Copied`:`Copy install command`,onClick:n[0]||=e=>s()},null,8,[`icon`,`label`])]),P(`div`,Pu,q(e.error.reason),1),I(ma,{class:`mt-2`,onClick:n[1]||=e=>r(`retry`)},{default:z(()=>[...n[4]||=[M(` Retry `,-1)]]),_:1})])]))}}),Iu={class:`devframes-view-iframe-loading absolute inset-0 flex flex-col items-center justify-center gap-2`},Lu=J({__name:`ViewIframeLoading`,setup(e){return(e,t)=>(G(),j(`div`,Iu,[...t[0]||=[P(`div`,{class:`i-ph:circle-notch-duotone animate-spin text-3xl color-faint`},null,-1),P(`div`,{class:`text-sm color-muted`},` Loading… `,-1)]]))}}),Ru={class:`w-full h-full flex flex-col`},zu={key:1,class:`flex items-center gap-1 px2 py1 rounded text-xs bg-amber/10 text-amber border border-amber/20 shrink-0`,title:`Cross-origin iframe`},Bu={class:`flex-1 flex items-center h-7 px-2.5 rounded bg-gray/5 border border-transparent hover:border-gray/10 focus-within:border-gray/15 transition-colors`},Vu=[`value`,`readonly`],Hu={key:0,class:`i-ph-circle-notch text-sm op40 ml-2 shrink-0 animate-spin`},Uu=40,Wu=J({__name:`ViewIframe`,props:{context:{},entry:{},panes:{},iframeStyle:{}},setup(e){let t=e,n=oe(t.context),r=N(()=>t.context.panel.store.mode===`edge`),i=N(()=>typeof t.entry.addressBar==`object`?t.entry.addressBar:void 0),a=N(()=>n.value.showIframeAddressBar||!!t.entry.addressBar),o=B(!0),s=B(!1),c=B(!1),l=B(null),u=N(()=>!l.value&&(o.value||s.value)),d=K(`viewFrame`),f=K(`urlInput`),p=B(t.entry.url),m=B(t.entry.url),h=B(!1),g=N(()=>t.entry.frameId??t.entry.id),_=N(()=>t.panes.get(g.value)?.iframe),v=N(()=>{try{return window.location.origin}catch{return``}}),y=N(()=>{try{return new URL(p.value).origin!==v.value}catch{return!0}}),b=N(()=>i.value?.back??!y.value),x=N(()=>i.value?.reload??!y.value),S=N(()=>i.value?.openExternal??!1),C=N(()=>{let e=re(p.value);if(y.value)return e;try{let t=new URL(e);return t.pathname+t.search+t.hash}catch{return e}});function w(e){let t=e.data;typeof t!=`object`||!t||t.type!==ce||e.source===_.value?.contentWindow&&(l.value={type:ce,package:String(t.package??``),version:String(t.version??``),reason:String(t.reason??``)})}function T(e){let t=_.value;if(!t)return;l.value=null;let n=e.trim();n&&!/^https?:\/\//i.test(n)&&(n=n.startsWith(`/`)?`${window.location.origin}${n}`:`http://${n}`),p.value=n,m.value=n,t.src=n,s.value=!0}function E(){h.value=!1,m.value!==p.value&&T(m.value)}function ee(){h.value=!0,m.value=p.value,Ae(()=>{f.value?.select()})}function te(){h.value=!1,m.value=p.value}function D(e){e.key===`Escape`&&(h.value=!1,m.value=p.value,f.value?.blur())}function ne(){try{_.value?.contentWindow?.history.back()}catch{}}function ie(){let e=_.value;if(!e)return;l.value=null,s.value=!0;let t=p.value;e.src=``,e.src=t,p.value=t,m.value=t}function ae(){try{let e=new URL(re(p.value));(e.protocol===`http:`||e.protocol===`https:`)&&window.open(e.href,`_blank`,`noopener,noreferrer`)}catch{}}let se,le;return Me(()=>{let e=t.panes.has(g.value),n=t.context.panel.consumeBootRoute?.(t.entry.id)??t.entry.url;!e&&n!==p.value&&(p.value=n,m.value=n);let i=t.panes.ensure(g.value,{src:n,style:{boxShadow:`none`,outline:`none`}}),u=i.iframe;le=ot({iframe:u,initial:p.value,onChange:e=>{p.value=e}}),e||(s.value=!0);let f=t.context.panel.session;$e(()=>{t.context.docks.selectedId===t.entry.id&&(f.selectedDockRoute=p.value)}),se=()=>{s.value=!1},u.addEventListener(`load`,se);let h=t.context.docks.getStateById(t.entry.id);h&&(h.domElements.iframe=u),$e(()=>{Object.assign(u.style,t.iframeStyle),a.value&&!r.value?(u.style.borderTopLeftRadius=`0px`,u.style.borderTopRightRadius=`0px`):(u.style.borderTopLeftRadius=``,u.style.borderTopRightRadius=``),r.value&&(u.style.borderRadius=`0px`,u.style.border=`none`)}),$e(()=>{c.value&&(l.value||s.value?i.hide():i.show())}),window.addEventListener(`message`,w),i.mount(d.value),o.value=!1,c.value=!0,Ae(()=>{i.update()})}),xe(()=>{window.removeEventListener(`message`,w),le?.(),le=void 0;let e=t.panes.get(g.value);e&&se&&e.iframe?.removeEventListener(`load`,se),e&&e.target===d.value&&e.unmount()}),(e,t)=>(G(),j(`div`,Ru,[a.value?(G(),j(`div`,{key:0,class:`flex-none px-2 w-full flex items-center gap-1 color-base border-base border-b`,style:R({height:`${Uu}px`})},[b.value?(G(),j(`button`,{key:0,class:`w-7 h-7 flex items-center justify-center rounded hover:bg-gray/15 transition-colors shrink-0`,title:`Back`,onClick:ne},[...t[1]||=[P(`div`,{class:`i-ph-caret-left op60 w-4.5 h-4.5`},null,-1)]])):A(``,!0),y.value?(G(),j(`div`,zu,[...t[2]||=[P(`div`,{class:`i-ph-globe text-sm`},null,-1),P(`span`,null,`Cross-Origin`,-1)]])):A(``,!0),x.value?(G(),j(`button`,{key:2,class:`w-7 h-7 flex items-center justify-center rounded hover:bg-gray/15 transition-colors shrink-0`,title:`Reload`,onClick:ie},[...t[3]||=[P(`div`,{class:`i-ph-arrow-clockwise op60 w-4.5 h-4.5`},null,-1)]])):A(``,!0),P(`div`,Bu,[P(`input`,{ref:`urlInput`,value:h.value?m.value:C.value,type:`text`,class:`flex-1 bg-transparent outline-none text-sm font-mono`,placeholder:`Enter URL...`,readonly:y.value,onInput:t[0]||=e=>m.value=e.target.value,onFocus:ee,onBlur:te,onKeydown:[D,ye(E,[`enter`])]},null,40,Vu),s.value?(G(),j(`div`,Hu)):A(``,!0)]),S.value?(G(),j(`button`,{key:3,class:`w-7 h-7 flex items-center justify-center rounded hover:bg-gray/15 transition-colors shrink-0`,title:`Open externally`,onClick:ae},[...t[4]||=[P(`div`,{class:`i-ph-arrow-square-out-duotone op60 w-4.5 h-4.5`},null,-1)]])):A(``,!0)],4)):A(``,!0),P(`div`,{ref_key:`viewFrame`,ref:d,class:`devframes-view-iframe relative w-full h-full flex-1 items-center justify-center`},[u.value?(G(),F(Lu,{key:0})):A(``,!0),l.value?(G(),F(Fu,{key:1,error:l.value,onRetry:ie},null,8,[`error`])):A(``,!0)],512)]))}}),Gu={class:`flex flex-col gap-4 items-center justify-center h-full relative`},Ku={class:`text-2xl font-bold`},qu={key:0,class:`flex flex-col gap-1 max-w-full w-64 items-start`},Ju=[`disabled`,`title`],Yu=[`value`],Xu={class:`min-h-16 max-w-full flex flex-col gap-2 items-center`},Zu={key:0,class:`max-w-full text-sm text-red-600 dark:text-red-400 text-center text-balance`},Qu={key:1,class:`inline-flex items-center gap-2 text-sm color-base op70`},$u={key:0,class:`i-ph-dots-three-outline-duotone animate-pulse flex-none`},ed={class:`truncate`},td=J({__name:`ViewLauncher`,props:{context:{},entry:{}},setup(e){let t=e,n=N(()=>t.entry.launcher.roots),r=B(n.value?.[0]?.value);Xe(n,e=>{let t=e?.[0];if(!t){r.value=void 0;return}e.some(e=>e.value===r.value)||(r.value=t.value)},{immediate:!0});function i(){let e=t.entry.launcher.command;e&&t.context.commands.execute(e,n.value?.length?{root:r.value}:void 0)}let a=N(()=>t.entry.launcher.status||`idle`),o=N(()=>t.entry.launcher.digest),s=N(()=>t.entry.launcher.terminalSessionId);function c(){s.value&&t.context.rpc.call(Ee.rpc.docksActivate,{dockId:p,params:{sessionId:s.value}})}let l={error:`i-ph-arrow-clockwise-duotone`,idle:`i-ph-rocket-launch-duotone`,loading:`i-ph-spinner-gap-duotone animate-spin`,success:`i-ph:check`},u=N(()=>t.entry.launcher.error),d=N(()=>a.value===`idle`?t.entry.launcher.buttonStart||`Launch`:a.value===`loading`?t.entry.launcher.buttonLoading||`Loading...`:a.value===`error`?`Retry`:a.value===`success`?`Success`:`UNKNOWN STATUS: ${a.value}`),f=N(()=>a.value===`idle`||a.value===`error`);return(t,p)=>(G(),j(`div`,Gu,[I(Ui,{icon:e.entry.launcher.icon||e.entry.icon,class:`w-10 h-10`},null,8,[`icon`]),P(`h1`,Ku,q(e.entry.launcher.title),1),P(`p`,null,q(e.entry.launcher.description),1),n.value?.length?(G(),j(`label`,qu,[p[1]||=P(`span`,{class:`text-xs op60`},`Launch root`,-1),Ze(P(`select`,{"onUpdate:modelValue":p[0]||=e=>r.value=e,disabled:a.value===`loading`,title:n.value.find(e=>e.value===r.value)?.description,class:`w-full px3 py2 text-sm rounded-lg bg-base color-base border border-base outline-none transition-all focus-visible:ring-3 focus-visible:ring-primary-500/30 disabled:op50 disabled:pointer-events-none`},[(G(!0),j(H,null,L(n.value,e=>(G(),j(`option`,{key:e.value,value:e.value},q(e.label),9,Yu))),128))],8,Ju),[[it,r.value]])])):A(``,!0),I(ma,{variant:`primary`,class:V([`min-w-40 justify-center`,a.value===`error`?`bg-error! border-error!`:``]),icon:l[a.value],loading:a.value===`loading`,disabled:!f.value,onClick:i},{default:z(()=>[M(q(d.value),1)]),_:1},8,[`class`,`icon`,`loading`,`disabled`]),P(`div`,Xu,[a.value===`error`&&u.value?(G(),j(`p`,Zu,q(u.value),1)):o.value?(G(),j(`div`,Qu,[a.value===`loading`?(G(),j(`div`,$u)):A(``,!0),P(`span`,ed,q(o.value),1)])):A(``,!0),s.value?(G(),F(ma,{key:2,variant:`text`,size:`sm`,icon:`i-ph-arrow-square-out-duotone`,onClick:c},{default:z(()=>[...p[2]||=[M(` View in Terminal `,-1)]]),_:1})):A(``,!0)])]))}}),nd={key:2},rd=J({__name:`ViewEntry`,props:{context:{},entry:{},panes:{},iframeStyle:{},divStyle:{}},setup(e){return(t,n)=>(G(),F(ge,null,{fallback:z(()=>[...n[0]||=[P(`div`,null,` Loading... `,-1)]]),default:z(()=>[e.entry.type===`~builtin`?(G(),j(H,{key:0},[e.entry.id===`~settings`?(G(),F(zs,{key:0,context:e.context,entry:e.entry},null,8,[`context`,`entry`])):e.entry.id===`~client-auth-notice`?(G(),F(_o,{key:1,context:e.context},null,8,[`context`])):(G(),j(`div`,nd,` Unknown builtin entry: `+q(e.entry),1))],64)):e.entry.type===`action`?(G(),j(H,{key:1},[],64)):e.entry.type===`iframe`?(G(),F(Wu,{key:2,context:e.context,entry:e.entry,panes:e.panes,"iframe-style":e.iframeStyle},null,8,[`context`,`entry`,`panes`,`iframe-style`])):e.entry.type===`custom-render`?(G(),F(Bs,{key:3,context:e.context,entry:e.entry,panes:e.panes,"div-style":e.divStyle},null,8,[`context`,`entry`,`panes`,`div-style`])):e.entry.type===`launcher`?(G(),F(td,{key:4,context:e.context,entry:e.entry},null,8,[`context`,`entry`])):(G(),F(qs,{key:5,context:e.context,entry:e.entry},null,8,[`context`,`entry`]))]),_:1}))}}),id=J({__name:`ColorSchemeRoot`,setup(e){return(e,t)=>(G(),j(`div`,{class:V([`devframes-color-root`,U(Be)?`dark`:`light`]),style:R({display:`contents`,colorScheme:U(Be)?`dark`:`light`})},[Pe(e.$slots,`default`)],6))}});function ad(e){return je(`button`,{class:`flex items-center gap-2 px3 py1.5 rounded hover:bg-active transition text-left`,onClick:e.action},[je(`div`,{class:`${e.icon} text-base op60`}),je(`span`,e.label)])}function od(e,t){let n=e.docks.settings,r=t.id;n.mutate(e=>{e.docksHidden.includes(r)||(e.docksHidden=[...e.docksHidden,r])}),e.docks.selected?.id===r&&e.docks.switchEntry(null),le(null)}function sd(e,t){let n=e.docks.getStateById(t.id)?.domElements.iframe;if(!n){le(null);return}let r=n.src;n.src=``,n.src=r,le(null)}function cd(e,t){return t.id!==`~settings`&&e.docks.entries.some(e=>e.id===t.id)}function ld(e){return e.type===`iframe`}function ud(e){let{context:t,entry:n,el:r,gap:i=6}=e,a=t.panel.store.mode===`edge`,o=[{label:`Hide`,icon:`i-ph-eye-slash-duotone`,action:()=>od(t,n),visible:cd(t,n)},{label:`Refresh`,icon:`i-ph-arrow-clockwise-duotone`,action:()=>sd(t,n),visible:ld(n)},{label:a?`Float Mode`:`Edge Mode`,icon:a?`i-ph-arrows-out-duotone`:`i-ph-square-half-bottom-duotone`,action:()=>{if(a){let e=t.panel.store;switch(e.position){case`bottom`:e.left=50,e.top=100;break;case`top`:e.left=50,e.top=0;break;case`left`:e.left=0,e.top=50;break;case`right`:e.left=100,e.top=50}e.mode=`float`}else t.panel.store.mode=`edge`;le(null)},visible:t.clientType===`embedded`},{label:`Popup`,icon:`i-ph-arrow-square-out-duotone`,action:()=>{le(null),se(t)},visible:ue()&&!ie().value&&t.clientType===`embedded`}].filter(e=>e.visible);o.length!==0&&le({el:r,gap:i,content:()=>je(`div`,{class:`flex flex-col text-sm min-w-36 mx--1`},o.map(ad))})}var dd=[`aria-label`],fd=J({__name:`DockEntry`,props:{context:{},dock:{},isAction:{type:Boolean},isSelected:{type:Boolean},isDimmed:{type:Boolean},isVertical:{type:Boolean},badge:{},badgeVariant:{},tooltip:{type:Boolean,default:!0}},setup(e){let t=e,n={info:{bg:`#3b82f6`,fg:`#eff6ff`},success:{bg:`#22c55e`,fg:`#f0fdf4`},warning:{bg:`#f59e0b`,fg:`#fffbeb`},danger:{bg:`#ef4444`,fg:`#fef2f2`}},r=N(()=>{if(!t.badgeVariant||t.badgeVariant==="default")return;let{bg:e,fg:r}=n[t.badgeVariant];return{backgroundColor:e,color:r}}),i=K(`button`);function a(){t.tooltip&&i.value&&w({content:t.dock.title,el:i.value})}function o(){t.tooltip&&w(null)}function s(e){if(!i.value||t.dock.id===`overflow`)return;e.preventDefault(),o();let n=t.context.docks.entries.find(e=>e.id===t.dock.id);n&&ud({context:t.context,entry:n,el:i.value,gap:6})}return Fe(`pointerdown`,()=>{t.tooltip&&w(null)}),(t,n)=>(G(),j(`div`,{key:e.dock.id,class:`relative group devframes-dock-entry`,onPointerenter:a,onPointerleave:o,onContextmenu:s},[P(`button`,{ref_key:`button`,ref:i,"aria-label":e.dock.title,class:V([[e.isVertical?`rotate-270`:``,e.isDimmed?`op50 saturate-0`:``,e.isSelected?`scale-120 text-primary`:``,e.isAction?`bg-[#8881] hover:bg-[#8882] rounded-full`:`rounded-xl`],`flex items-center justify-center p1.5 hover:bg-[#8881] hover:scale-110 transition-all duration-300 relative outline-none`])},[I(Ui,{icon:e.dock.icon,class:`w-5 h-5 select-none`},null,8,[`icon`]),e.badge?(G(),j(`div`,{key:0,class:V([`absolute top-0.5 right-0 text-0.6em px-1 rounded-full shadow`,r.value?``:`bg-gray-500 text-white`]),style:R(r.value)},q(e.badge),7)):A(``,!0)],10,dd)],32))}});function pd(e){return e?{"--devframe-primary":e}:void 0}var md={class:`truncate`},hd={key:0,class:`border-t border-base mx--2 my1`},gd=[`onClick`],_d={class:`truncate flex-1`},vd={key:0,class:`bg-gray-6 text-white text-0.6em px-1 rounded-full shadow`},yd={key:0,class:`px2 py1.5 op50 text-sm italic`},bd=J({__name:`DockGroupPopover`,props:{context:{},group:{},members:{},selectedId:{}},emits:[`select`],setup(e,{emit:t}){let n=e,r=t,i=N(()=>n.members.every(([,e])=>e.length===0));return(t,n)=>(G(),j(`div`,{class:V([`flex flex-col gap-0.5 min-w-44 max-w-64`,e.group.accentColor?`devframes-accent-scope`:``]),style:R(U(pd)(e.group.accentColor)),onMousemove:n[0]||=Te(()=>{},[`stop`])},[P(`div`,{class:V([`flex items-center gap-1.5 px4 mx--2 pt2 pb2.5 font-bold text-2.75 uppercase tracking-wide border-b border-base`,e.group.accentColor?`color-active`:``])},[I(Ui,{icon:e.group.icon,class:`w-4.5 h-4.5`},null,8,[`icon`]),P(`span`,md,q(e.group.title),1)],2),(G(!0),j(H,null,L(e.members,([t,n],i)=>(G(),j(H,{key:t},[i>0&&n.length?(G(),j(`div`,hd)):A(``,!0),(G(!0),j(H,null,L(n,t=>(G(),j(`button`,{key:t.id,class:V([`flex items-center gap-2 w-full px2 py1.5 rounded text-sm text-left transition`,e.selectedId===t.id?`text-primary bg-active`:`op80 hover:op100 hover:bg-active`]),onClick:e=>r(`select`,t)},[I(Ui,{icon:t.icon,class:`w-4.5 h-4.5 flex-none`},null,8,[`icon`]),P(`span`,_d,q(t.title),1),t.badge?(G(),j(`div`,vd,q(t.badge),1)):A(``,!0)],10,gd))),128))],64))),128)),i.value?(G(),j(`div`,yd,` No tools yet `)):A(``,!0)],38))}}),xd=J({__name:`DockGroupButton`,props:{context:{},group:{},isVertical:{type:Boolean},selected:{},dimInactive:{type:Boolean,default:!0}},emits:[`select`],setup(e,{emit:t}){let r=e,i=t,a=oe(r.context),o=N(()=>n(r.context.docks.entries,r.group.id,a.value,{whenContext:r.context.when.context})),s=N(()=>v(r.context.docks.entries,r.group.id,a.value,{whenContext:r.context.when.context})),l=N(()=>{let e=r.selected?.id;return!!e&&o.value.some(t=>t.id===e)}),u=K(`groupButton`),d=B(!1),f=ne();function p(){u.value&&(d.value=!0,S({el:u.value,content:()=>je(bd,{context:r.context,group:r.group,members:s.value,selectedId:r.selected?.id??null,onSelect:e=>{i(`select`,e),m()}})}))}function m(){d.value=!1,S(null)}function h(){d.value?m():p()}Ie(()=>f.value,e=>{d.value=e?.el===u.value},{debounce:1e3});function g(){if(l.value){m(),i(`select`,void 0);return}let e=c(r.context.docks.entries,r.group,r.context.panel.session.groupLastChildIds?.[r.group.id],r.context.when.context);if(e){m(),i(`select`,e);return}h()}return(t,n)=>(G(),j(`div`,{ref_key:`groupButton`,ref:u,class:V(e.group.accentColor?`devframes-accent-scope`:``),style:R(U(pd)(e.group.accentColor))},[I(fd,{context:e.context,dock:e.group,"is-vertical":e.isVertical,"is-selected":l.value||d.value,"is-dimmed":e.dimInactive&&e.selected?!l.value:!1,badge:e.group.badge,"badge-variant":e.group.badgeVariant,onClick:g},null,8,[`context`,`dock`,`is-vertical`,`is-selected`,`is-dimmed`,`badge`,`badge-variant`])],6))}}),Sd=J({__name:`DockEntries`,props:{context:{},entries:{},selected:{},isVertical:{type:Boolean},dimInactive:{type:Boolean,default:!0}},emits:[`select`],setup(t,{emit:r}){let i=t,a=r,{selected:s,isVertical:c,entries:l}=W(i),u=oe(i.context);function d(t){return!(t.type===`group`&&n(i.context.docks.entries,t.id,u.value,{whenContext:i.context.when.context}).length===0&&!o(i.context.docks.entries,t.id,t.defaultChildId,i.context.when.context)||t.when&&!e(t.when,i.context.when.context)||t.visibility&&!e(t.visibility,i.context.when.context))}function f(e){s.value?.id===e.id?a(`select`,void 0):a(`select`,e)}return(e,n)=>(G(!0),j(H,null,L(U(l),e=>(G(),j(H,{key:e.id},[d(e)?(G(),j(H,{key:0},[e.type===`group`?(G(),F(xd,{key:0,context:t.context,group:e,"is-vertical":U(c),selected:U(s),"dim-inactive":t.dimInactive,onSelect:n[0]||=e=>a(`select`,e)},null,8,[`context`,`group`,`is-vertical`,`selected`,`dim-inactive`])):(G(),F(fd,{key:1,context:t.context,dock:e,"is-action":e.type===`action`,"is-selected":U(s)?.id===e.id,"is-dimmed":t.dimInactive&&U(s)?U(s).id!==e.id:!1,"is-vertical":U(c),badge:e.badge,"badge-variant":e.badgeVariant,onClick:t=>f(e)},null,8,[`context`,`dock`,`is-action`,`is-selected`,`is-dimmed`,`is-vertical`,`badge`,`badge-variant`,`onClick`]))],64)):A(``,!0)],64))),128))}}),Cd=J({__name:`DockEntriesWithCategories`,props:{context:{},groups:{},selected:{},isVertical:{type:Boolean},rotate:{type:Boolean,default:!0},dimInactive:{type:Boolean,default:!0}},emits:[`select`],setup(e,{emit:t}){let n=t;return(t,r)=>(G(!0),j(H,null,L(e.groups,([i,a],o)=>(G(),j(H,{key:i},[o>0?Pe(t.$slots,`separator`,{category:i,index:o,isVertical:e.isVertical},()=>[P(`div`,{class:V([`border-base m1`,e.isVertical&&!e.rotate?`w-20px h-px border-b-1.5`:`h-20px w-px border-r-1.5`])},null,2)],void 0,0):A(``,!0),I(Sd,{context:e.context,entries:a,"is-vertical":e.isVertical&&e.rotate,selected:e.selected,"dim-inactive":e.dimInactive,onSelect:r[0]||=e=>n(`select`,e)},null,8,[`context`,`entries`,`is-vertical`,`selected`,`dim-inactive`])],64))),128))}}),wd={key:0,class:`w-8 h-px border-t border-base my0.5`},Td=[`aria-label`,`onPointerenter`,`onClick`],Ed={key:0,class:`absolute top-0.5 right-0.5 bg-gray-6 text-white text-0.6em px-0.5 rounded-full shadow leading-none`},Dd={class:`absolute top-0.5 right-0.5 bg-gray-6 text-white text-0.6em px-0.5 rounded-full shadow leading-none`},Od=34,kd=34,Ad=7,jd=53,Md=J({__name:`DockGroupSidebar`,props:{context:{},group:{},selectedId:{}},setup(e){let t=e,n=oe(t.context),r=N(()=>v(t.context.docks.entries,t.group.id,n.value,{whenContext:t.context.when.context})),i=K(`sidebar`),o=B(0);Xe(i,(e,t,n)=>{if(!e){o.value=0;return}let r=e.ownerDocument?.defaultView?.ResizeObserver??globalThis.ResizeObserver;if(!r)return;let i=new r(([e])=>{e&&(o.value=e.borderBoxSize?.[0]?.blockSize??e.contentRect.height)});i.observe(e),n(()=>i.disconnect())},{immediate:!0,flush:`post`});let s=N(()=>r.value.reduce((e,[,t])=>e+t.length,0)),c=N(()=>a({availableHeight:o.value,reservedHeight:jd,itemHeight:Od,dividerHeight:Ad,moreButtonHeight:kd,dividerCount:Math.max(0,r.value.filter(([,e])=>e.length).length-1),totalItems:s.value})),u=N(()=>l(r.value,c.value)),d=N(()=>u.value.visible),f=N(()=>u.value.overflow),p=N(()=>f.value.reduce((e,[,t])=>e+t.length,0)),m=N(()=>p.value>0),h=N(()=>p.value>9?`9+`:p.value.toString()),g=N(()=>!!t.selectedId&&f.value.some(([,e])=>e.some(e=>e.id===t.selectedId)));function _(e){t.context.docks.switchEntry(e)}function y(e,t){w({content:t,el:e.currentTarget})}function b(){w(null)}let x=K(`moreButton`),S=B(!1),C=T();function E(){x.value&&(S.value=!0,O({el:x.value,placement:`right`,content:()=>je(bd,{context:t.context,group:t.group,members:f.value,selectedId:t.selectedId,onSelect:e=>{_(e.id),ee()}})}))}function ee(){S.value=!1,O(null)}function te(){S.value?ee():E()}Ie(()=>C.value,e=>{S.value=e?.el===x.value},{debounce:1e3});let D=N(()=>g.value||S.value);return(t,n)=>(G(),j(`div`,{ref_key:`sidebar`,ref:i,class:V([`devframes-group-sidebar flex flex-col flex-none w-12 h-full border-r border-base of-y-hidden of-x-hidden select-none py1.5 items-center gap-0.5`,e.group.accentColor?`devframes-accent-scope`:``]),style:R(U(pd)(e.group.accentColor))},[P(`div`,{class:V([`flex items-center justify-center w-8 h-8 op60`,e.group.accentColor?`color-active`:``]),onPointerenter:n[0]||=t=>y(t,e.group.title),onPointerleave:b},[I(Ui,{icon:e.group.icon,class:`w-5 h-5 flex-none`},null,8,[`icon`])],34),n[2]||=P(`div`,{class:`w-8 h-px border-t border-base my0.5`},null,-1),(G(!0),j(H,null,L(d.value,([t,n],r)=>(G(),j(H,{key:t},[r>0&&n.length?(G(),j(`div`,wd)):A(``,!0),(G(!0),j(H,null,L(n,t=>(G(),j(`button`,{key:t.id,"aria-label":t.title,class:V([`relative flex items-center justify-center w-8 h-8 rounded-lg transition`,e.selectedId===t.id?`text-primary bg-active`:`op60 hover:op100 hover:bg-active`]),onPointerenter:e=>y(e,t.title),onPointerleave:b,onPointerdown:b,onClick:e=>_(t.id)},[I(Ui,{icon:t.icon,class:`w-5 h-5 flex-none`},null,8,[`icon`]),t.badge?(G(),j(`div`,Ed,q(t.badge),1)):A(``,!0)],42,Td))),128))],64))),128)),m.value?(G(),j(`button`,{key:0,ref_key:`moreButton`,ref:x,"aria-label":`Show more`,class:V([`relative flex items-center justify-center w-8 h-8 rounded-lg transition mt-auto flex-none`,D.value?`text-primary bg-active`:`op60 hover:op100 hover:bg-active`]),onPointerenter:n[1]||=e=>y(e,`Show more`),onPointerleave:b,onPointerdown:b,onClick:te},[I(Ui,{icon:`ph:dots-three-circle-duotone`,class:`w-5 h-5 flex-none`}),P(`div`,Dd,q(h.value),1)],34)):A(``,!0)],6))}}),Nd={key:0,class:`h-screen w-screen of-hidden`},Pd={key:1,class:`h-screen w-screen of-hidden grid cols-[max-content_1fr]`},Fd={class:`border-r border-base flex flex-col min-h-0`},Id={class:`p2 border-b border-base flex`},Ld={class:`transition duration-200 p2 of-y-auto`},Rd={class:`min-h-0 flex`},zd={class:`relative flex-1 min-w-0 min-h-0`},Bd=ve(J({__name:`DockStandalone`,props:{context:{}},setup(e){let t=e.context,n=K(`viewsContainer`),r=ft(n,t.panel,()=>dt(t.docks.selected)),i=pt(t,e=>{e||t.docks.switchEntry(null)});Xe(()=>t.docks.entries,()=>{t.docks.selectedId||=t.docks.entries[0]?.id??null},{immediate:!0});let a=N(()=>t.docks.groupedEntries),o=N(()=>me(t.docks.entries,t.docks.selected));function s(e){e&&t.docks.switchEntry(e)}return(e,c)=>(G(),F(id,null,{default:z(()=>[U(i)?(G(),j(`div`,Pd,[P(`div`,Fd,[P(`div`,Id,[I(ur,{class:`w-7 h-7 ma`})]),P(`div`,Ld,[I(Cd,{context:U(t),groups:a.value,"is-vertical":!1,selected:U(t).docks.selected,"dim-inactive":!1,onSelect:c[0]||=e=>s(e?.id)},{separator:z(()=>[...c[1]||=[P(`div`,{class:`border-base border-b w-full my-2`},null,-1)]]),_:1},8,[`context`,`groups`,`selected`])])]),P(`div`,Rd,[o.value?(G(),F(Md,{key:0,context:U(t),group:o.value,"selected-id":U(t).docks.selected?.id??null},null,8,[`context`,`group`,`selected-id`])):A(``,!0),P(`div`,zd,[U(t).docks.selected&&U(r)?(G(),F(rd,{key:U(t).docks.selected.id,entry:U(t).docks.selected,context:U(t),panes:U(r)},null,8,[`entry`,`context`,`panes`])):A(``,!0),P(`div`,{id:`devframes-views-container`,ref_key:`viewsContainer`,ref:n,class:`pointer-events-auto`},null,512)])])])):(G(),j(`div`,Nd,[I(_o,{context:U(t)},null,8,[`context`])])),I(ao),I(fa,{context:U(t)},null,8,[`context`]),I(qa,{context:U(t)},null,8,[`context`]),I(Sa)]),_:1}))}}),{shadowRoot:!0,styles:[ct]});customElements.get(`devframes-dock-standalone`)||customElements.define(`devframes-dock-standalone`,Bd);export{Bd as DockStandalone}; -\ No newline at end of file -+`+u),Ee&&(u=ht(u)),w&&L?ne(u):u},t.setConfig=function(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};K(e),I=!0,Oe=k,ke=A},t.clearConfig=function(){G=null,I=!1,Oe=null,ke=null,w=E,T=``},t.isValidAttribute=function(e,t,n){G||K({});let r=W(e),i=W(t);return Et(r,i,n)},t.addHook=function(e,t){typeof t==`function`&&Wr(O,e)&&jr(O[e],t)},t.removeHook=function(e,t){if(Wr(O,e)){if(t!==void 0){let n=kr(O[e],t);return n===-1?void 0:Mr(O[e],n,1)[0]}return Ar(O[e])}},t.removeHooks=function(e){Wr(O,e)&&(O[e]=[])},t.removeAllHooks=function(){O=ji()},t}var Fi=Pi(),Ii=new Map,Li=Fi();async function Ri(e,t){let n=`${e}:${t}`;if(Ii.has(n))return Ii.get(n);let r=i().then(e=>(Ii.set(n,e),e)).catch(e=>{throw Ii.delete(n),e});return Ii.set(n,r),r;async function i(){let n=`https://api.iconify.design/${e}/${t}.svg?color=currentColor&width=100%`,r=await fetch(n,{signal:AbortSignal.timeout(1e4)}).then(e=>e.text());return Li.sanitize(r)}}var zi=[`innerHTML`],Bi=[`src`],Vi=J({__name:`IconifyIcon`,props:{icon:{}},setup(e){let t=e,n=N(()=>t.icon.includes(`/`)||t.icon.startsWith(`data:`)||t.icon.startsWith(`builtin:`)),r=N(()=>{if(n.value)return;let e=t.icon.match(/^(?:i-)?([\w-]+):([\w-]+)$/);if(e)return{collection:e[1],icon:e[2]}}),i=B(void 0);return $e(async()=>{if(!r.value){i.value=void 0;return}try{i.value=await Ri(r.value.collection,r.value.icon)}catch{i.value=void 0}}),(t,n)=>r.value?(G(),j(`div`,{key:0,innerHTML:i.value},null,8,zi)):(G(),j(`img`,{key:1,src:e.icon,class:`w-full h-full m-auto`,draggable:`false`},null,8,Bi))}}),Hi={key:1,class:`flex items-center justify-center`},Ui=J({__name:`DockIcon`,props:{icon:{},title:{}},setup(e){let t=e,n=N(()=>typeof t.icon==`string`?{dark:t.icon,light:t.icon}:t.icon);return(t,r)=>n.value.light===`builtin:devframes`?(G(),F(ur,{key:0})):(G(),j(`div`,Hi,[n.value.light===n.value.dark?(G(),F(Vi,{key:0,icon:n.value.light,title:e.title},null,8,[`icon`,`title`])):(G(),j(H,{key:1},[I(Vi,{class:`dark-hidden`,icon:n.value.light,title:e.title},null,8,[`icon`,`title`]),I(Vi,{class:`light-hidden`,icon:n.value.dark,title:e.title},null,8,[`icon`,`title`])],64))]))}}),Wi=[`id`],Gi={class:`flex items-center gap-2 flex-1 of-hidden min-w-0`},Ki={class:`flex-1 min-w-0`},qi={class:`flex items-center gap-1.5`},Ji={class:`truncate`},Yi={key:0,class:`op50`},Xi={key:0,class:`text-[10px] px-1 py-0 rounded bg-blue/10 text-blue shrink-0 leading-4`},Zi={key:0,class:`truncate text-xs op40 mt-0.5`},Qi={class:`flex items-center gap-1.5 flex-none`},$i={key:0,class:`i-ph-spinner-gap-duotone w-3.5 h-3.5 animate-spin op50`},ea={key:1,class:`i-ph-caret-right w-3 h-3 op40`},ta=J({__name:`CommandPaletteItem`,props:{entry:{},parentTitle:{},showParentTitle:{type:Boolean},selected:{type:Boolean},loading:{type:Boolean},keybindings:{}},emits:[`select`,`activate`],setup(e){return(t,n)=>(G(),j(`button`,{id:`cmd-${e.entry.id}`,class:`w-full text-left`,onClick:n[0]||=e=>t.$emit(`activate`),onMouseover:n[1]||=e=>t.$emit(`select`)},[P(`div`,{class:V([`flex items-center gap-2 justify-between rounded-md px-2.5 py-1.5 text-sm transition-colors`,e.selected?`bg-primary/10 text-primary`:`op80 hover:op100`])},[P(`div`,Gi,[e.entry.icon?(G(),F(Ui,{key:0,icon:e.entry.icon,class:`w-4 h-4 flex-none op70`},null,8,[`icon`])):A(``,!0),P(`div`,Ki,[P(`div`,qi,[P(`span`,Ji,[e.parentTitle&&e.showParentTitle?(G(),j(`span`,Yi,q(e.parentTitle)+` › `,1)):A(``,!0),M(` `+q(e.entry.title),1)]),e.entry.source===`server`?(G(),j(`span`,Xi,`server`)):A(``,!0)]),e.selected&&e.entry.description?(G(),j(`div`,Zi,q(e.entry.description),1)):A(``,!0)])]),P(`div`,Qi,[(G(!0),j(H,null,L(e.keybindings,(e,t)=>(G(),F(sr,{key:t,keys:e.key},null,8,[`keys`]))),128)),e.loading?(G(),j(`span`,$i)):e.entry.children?.length?(G(),j(`span`,ea)):A(``,!0)])],2)],40,Wi))}}),na={class:`absolute inset-0 flex items-start justify-center relative pt-[20vh] pointer-events-none`},ra={class:`w-full w-lg bg-glass:80 color-base border border-base rounded-lg shadow-xl pointer-events-auto of-hidden flex flex-col max-h-[60vh]`},ia={class:`border-b border-base flex items-center px-3`},aa=[`onClick`],oa={key:0,class:`py-8 flex flex-col items-center justify-center gap-2 op50 text-sm`},sa={key:0},ca={class:`text-primary op100`},la={key:1},ua={class:`border-t border-base flex items-center justify-between gap-4 px-3 py-1.5 text-[10px] op50`},da={class:`flex items-center gap-1.5`},fa=J({__name:`CommandPalette`,props:{context:{}},setup(e){let t=e,n=N(()=>t.context.commands),i=N({get:()=>n.value.paletteOpen,set:e=>{n.value.paletteOpen=e}}),a=B(``),o=B(0),s=K(`searchInput`),c=K(`listContainer`),l=B(!1),u=B([]),d=N(()=>x(n.value.paletteCommands)),f=N(()=>u.value.length>0?u.value.at(-1).items.map(e=>({entry:e,searchTitle:e.title})):d.value),p=B(),m=N(()=>p.value?p.value.map(e=>({entry:e,searchTitle:e.title})):f.value),_=N(()=>new zn(m.value,{keys:[`searchTitle`,`entry.description`,`entry.id`],distance:50,threshold:.4})),v=N(()=>a.value?_.value.search(a.value).map(e=>e.item):m.value);Xe(a,()=>{o.value=0});function y(e){a.value=``,o.value=0,p.value=void 0;let t=h(n.value.paletteCommands,e);u.value=t,e!=null&&!t.some(t=>t.id===e)&&(n.value.paletteScopeId=null)}Xe(i,e=>{e?(y(n.value.paletteScopeId),requestAnimationFrame(()=>{l.value=!0}),Ae(()=>s.value?.focus())):(l.value=!1,n.value.paletteScopeId=null)}),Xe(()=>n.value.paletteScopeId,e=>{i.value&&y(e)}),Xe(()=>n.value.paletteCommands,e=>{if(!i.value||u.value.length===0)return;let t=n.value.paletteScopeId,r=t!=null&&u.value.some(e=>e.id===t),a=fe(e,u.value,t);u.value=a,o.value=Math.min(o.value,Math.max(v.value.length-1,0)),r&&t!=null&&!a.some(e=>e.id===t)&&(n.value.paletteScopeId=null)});function b(e){let t=v.value.length;t!==0&&(o.value=(o.value+e+t)%t,S())}function S(){let e=v.value[o.value];e&&((c.value?.getRootNode())?.getElementById(`cmd-${e.entry.id}`))?.scrollIntoView({block:`nearest`})}let C=B(null);async function w(e){let r=m.value.find(t=>t.entry.id===e.entry.id)?.entry;if(r){if(g(r,t.context.docks.entries)===`drill`){n.value.paletteScopeId=r.id;return}if(r.source===`client`&&r.action){try{let e=await r.action();if(Array.isArray(e)){p.value=e,a.value=``,o.value=0;return}}catch(e){console.error(`[@devframes/hub-ui] Command "${r.id}" failed:`,e)}if(pe(r,t.context.docks.entries,n.value.paletteOpen,n.value.paletteScopeId))return;T();return}if(r.source===`server`){C.value=r.id;try{await n.value.execute(r.id)}catch(e){console.error(`[@devframes/hub-ui] Command "${r.id}" failed:`,e)}finally{C.value=null}T();return}T()}}function T(){l.value=!1,setTimeout(()=>{i.value=!1,a.value=``,u.value=[],p.value=void 0},150)}function E(){if(p.value){p.value=void 0,a.value=``,o.value=0;return}if(u.value.length>0){u.value.pop(),n.value.paletteScopeId=r(u.value),a.value=``,o.value=0;return}T()}function ee(e){u.value.splice(e),n.value.paletteScopeId=r(u.value),a.value=``,o.value=0}function te(e){e.key===`Backspace`&&!a.value&&(u.value.length>0||p.value)&&(e.preventDefault(),E())}function D(e){if(i.value)switch(e.key){case`ArrowDown`:case`ArrowUp`:e.preventDefault(),b(e.key===`ArrowDown`?1:-1);break;case`Enter`:{let t=v.value[o.value];t&&(e.preventDefault(),w(t));break}case`Escape`:e.preventDefault(),u.value.length>0||p.value?E():T()}}function ne(e){return n.value.getKeybindings(e)}return(e,t)=>i.value?(G(),j(`div`,{key:0,class:`devframes-command-palette fixed inset-0 z-command-palette`,onKeydown:D},[P(`div`,{class:V([`absolute inset-0 bg-white/50 dark:bg-black/30 transition-opacity duration-150`,l.value?`opacity-100 backdrop-blur-1`:`opacity-0 backdrop-blur-0`]),onClick:T},null,2),P(`div`,na,[P(`div`,{class:V([`flex flex-col transition-all duration-150`,l.value?`opacity-100 scale-100 translate-y-0`:`opacity-0 scale-98 -translate-y-2`])},[I(qn,{class:`absolute top--32px left-5px pointer-events-none`}),P(`div`,ra,[P(`header`,ia,[u.value.length>0?(G(!0),j(H,{key:0},L(u.value,(e,n)=>(G(),j(`button`,{key:n,class:`text-xs op60 hover:op80 mr-1 flex items-center gap-0.5`,onClick:e=>ee(n)},[M(q(e.title)+` `,1),t[1]||=P(`span`,{class:`op40`},`›`,-1)],8,aa))),128)):A(``,!0),Ze(P(`input`,{ref_key:`searchInput`,ref:s,"onUpdate:modelValue":t[0]||=e=>a.value=e,class:`flex-1 bg-transparent py-3 outline-none text-sm color-base`,placeholder:`Type a command...`,onKeydown:te},null,544),[[ke,a.value]])]),P(`div`,{ref_key:`listContainer`,ref:c,class:`flex-1 of-y-auto p-1.5`},[(G(!0),j(H,null,L(v.value,(e,t)=>(G(),F(ta,{key:e.entry.id,entry:e.entry,"parent-title":e.parentTitle,"show-parent-title":!u.value.length,selected:o.value===t,loading:C.value===e.entry.id,keybindings:ne(e.entry.id),onSelect:e=>o.value=t,onActivate:t=>w(e)},null,8,[`entry`,`parent-title`,`show-parent-title`,`selected`,`loading`,`keybindings`,`onSelect`,`onActivate`]))),128)),v.value.length?A(``,!0):(G(),j(`div`,oa,[t[4]||=P(`div`,{class:`i-ph-magnifying-glass-duotone w-6 h-6`},null,-1),a.value?(G(),j(`div`,sa,[t[2]||=M(` No results for "`,-1),P(`strong`,ca,q(a.value),1),t[3]||=M(`" `,-1)])):(G(),j(`div`,la,` No commands available `))]))],512),P(`footer`,ua,[t[6]||=P(`div`,{class:`flex items-center gap-1.5`},[P(`kbd`,{class:`px-1 py-0.5 rounded border border-base bg-base font-mono`},`↓↑`),P(`span`,null,`navigate`)],-1),P(`div`,da,[t[5]||=P(`kbd`,{class:`px-1 py-0.5 rounded border border-base bg-base font-mono`},`esc`,-1),P(`span`,null,q(u.value.length>0||p.value?`back`:`close`),1)]),t[7]||=P(`div`,{class:`flex items-center gap-1.5`},[P(`kbd`,{class:`px-1 py-0.5 rounded border border-base bg-base font-mono`},`↵`),P(`span`,null,`select`)],-1)])])],2)])],32)):A(``,!0)}}),pa=J({__name:`FeedbackSpinner`,props:{size:{default:`1em`}},setup(e){let t=e,n=N(()=>typeof t.size==`number`?`${t.size}px`:t.size);return(e,t)=>(G(),j(`span`,{class:`border-2 border-current border-t-transparent rounded-full inline-block animate-spin`,style:R({width:n.value,height:n.value}),role:`status`,"aria-label":`Loading`},null,4))}}),ma=J({__name:`ActionButton`,props:{as:{},to:{},href:{},icon:{},variant:{default:`action`},size:{default:`md`},loading:{type:Boolean},disabled:{type:Boolean}},setup(e){let t=e,n=N(()=>t.as?t.as:t.href!=null||t.to!=null?`a`:`button`),r=N(()=>n.value===`a`),i=N(()=>n.value===`button`),a=N(()=>t.size===`sm`),o=N(()=>t.variant===`primary`?a.value?`btn-primary text-sm`:`btn-primary`:t.variant===`text`?a.value?`btn-text text-sm`:`btn-text`:a.value?`btn-action-sm`:`btn-action`),s=N(()=>t.disabled||t.loading);return(t,a)=>(G(),F(he(n.value),{class:V([o.value,{"pointer-events-none op-mute":s.value&&!i.value}]),href:r.value?e.href??e.to:void 0,to:e.as&&e.to!=null?e.to:void 0,disabled:i.value?s.value:void 0,"aria-disabled":s.value||void 0,"aria-busy":e.loading||void 0},{default:z(()=>[e.loading?(G(),F(pa,{key:0,size:`1em`})):e.icon?(G(),j(`span`,{key:1,class:V(e.icon),"aria-hidden":`true`},null,2)):A(``,!0),Pe(t.$slots,`default`)]),_:3},8,[`class`,`href`,`to`,`disabled`,`aria-disabled`,`aria-busy`]))}}),ha=Ye({singleton:!0});function ga(){return ha.start}var _a=[`aria-labelledby`,`onKeydown`],va=[`onClick`],ya={class:`relative w-full max-w-96 bg-base border border-base rounded-lg shadow-xl p-5`},ba={key:0,id:`devframes-confirm-title`,class:`text-sm font-medium leading-5`},xa={class:`flex items-center justify-end gap-2 mt-6`},Sa=J({__name:`Confirm`,setup(e){let t=K(`confirmButton`);Ge(()=>{(t.value?.$el)?.focus({preventScroll:!0})});function n(e,t){e(t)}return(e,r)=>(G(),F(U(ha),null,{default:z(({resolve:e,args:[r]})=>[P(`div`,{class:`devframes-confirm fixed inset-0 z-dock-confirm flex items-center justify-center p-4 color-base`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":r.title?`devframes-confirm-title`:void 0,"aria-describedby":`devframes-confirm-message`,onKeydown:ye(Te(t=>n(e,!1),[`prevent`,`stop`]),[`esc`])},[P(`div`,{class:`absolute inset-0 bg-black/30 dark:bg-black/45 backdrop-blur-1 cursor-default`,"aria-hidden":`true`,onClick:t=>n(e,!1)},null,8,va),P(`div`,ya,[r.title?(G(),j(`h3`,ba,q(r.title),1)):A(``,!0),P(`p`,{id:`devframes-confirm-message`,class:V([`text-xs op60 leading-5`,r.title?`mt-1.5`:``])},q(r.message),3),P(`div`,xa,[I(ma,{variant:`text`,size:`sm`,onClick:t=>n(e,!1)},{default:z(()=>[M(q(r.cancelText??`Cancel`),1)]),_:2},1032,[`onClick`]),I(ma,{ref_key:`confirmButton`,ref:t,variant:`primary`,size:`sm`,onClick:t=>n(e,!0)},{default:z(()=>[M(q(r.confirmText??`OK`),1)]),_:2},1032,[`onClick`])])])],40,_a)]),_:1}))}}),Ca=nt([]),wa=new Map;function Ta(){return Ca}function Ea(e){let t=Ca.find(t=>t.id===e.id);if(t){t.entry=e;let n=wa.get(e.id);n&&(clearTimeout(n),wa.delete(e.id));let r=e.autoDismiss??5e3;r!==!1&&wa.set(e.id,setTimeout(Da,r,e.id));return}let n={id:e.id,entry:e};Ca.push(n);let r=e.autoDismiss??5e3;r!==!1&&wa.set(e.id,setTimeout(Da,r,e.id))}function Da(e){let t=Ca.findIndex(t=>t.id===e);t!==-1&&Ca.splice(t,1);let n=wa.get(e);n&&(clearTimeout(n),wa.delete(e))}function Oa(e,t,n){return e.notify?t?JSON.stringify(e)!==JSON.stringify(t):!n||e.status===`loading`:!1}var ka;function Aa(e){if(ka)return ka;let t=ka=nt({entries:[],unreadCount:0,pendingSelectId:null}),n=new Map,r=!0,i=null;async function a(){let a=await e.rpc.call(`devframes:plugin:messages:list`,i),o=0;let removedIds=a.removedIds;if(a.full){const retainedIds=new Set(a.entries.map(entry=>entry.id));removedIds=[...n.keys()].filter(id=>!retainedIds.has(id))}for(const id of removedIds){n.delete(id);Da(id)}for(let e of a.entries){let t=n.get(e.id);t||o++,Oa(e,t,r)&&Ea(e),n.set(e.id,e)}t.entries=Array.from(n.values()),t.unreadCount+=o,i=a.version,r=!1}let o=()=>a().catch(()=>{});return e.rpc.client.register({name:`devframe:messages:updated`,type:`action`,handler:()=>{e.rpc.isTrusted&&o()}}),e.rpc.ensureTrusted().then(()=>o()),t}function ja(e){ka&&(ka.pendingSelectId=e)}var Ma={info:{icon:`i-ph:info-duotone`,color:`text-blue`,bg:`bg-blue`,label:`Info`},warn:{icon:`i-ph:warning-duotone`,color:`text-amber`,bg:`bg-amber`,label:`Warning`},error:{icon:`i-ph:x-circle-duotone`,color:`text-red`,bg:`bg-red`,label:`Error`},success:{icon:`i-ph:check-circle-duotone`,color:`text-green`,bg:`bg-green`,label:`Success`},debug:{icon:`i-ph:bug-duotone`,color:`text-gray`,bg:`bg-gray`,label:`Debug`}};function Na(e,t=1){let n=0;for(let t=0;t({color:Na(t.label),backgroundColor:Na(t.label,.1)}));return(t,r)=>(G(),j(`span`,{class:`text-xs px-1 rounded`,style:R(n.value)},q(e.label),5))}}),Fa={class:`flex items-start gap-2 relative`},Ia={key:0,class:`flex-none mt-0.5 border-2 border-current border-t-transparent rounded-full animate-spin op50 w-4 h-4`},La={class:`flex-1 min-w-0 space-y-0.5`},Ra={class:`flex items-center gap-2`},za=[`title`],Ba={key:0,class:`text-xs op80 whitespace-pre-wrap`},Va={key:1,class:`flex flex-wrap items-center gap-x-2 gap-y-0.5`},Ha=J({__name:`MessageItem`,props:{entry:{},compact:{type:Boolean}},setup(e){let t=e,n=rt(()=>t.entry.timestamp);return(t,r)=>(G(),j(`div`,Fa,[P(`div`,{class:V([`w-2px flex-none absolute left-0 top-4px bottom-4px rounded-r`,[U(Ma)[e.entry.level]?.bg||`bg-gray`]])},null,2),e.entry.status===`loading`?(G(),j(`div`,Ia)):(G(),j(`div`,{key:1,class:V([`flex-none mt-0.5 w-4 h-4`,[U(Ma)[e.entry.level]?.icon,U(Ma)[e.entry.level]?.color]])},null,2)),P(`div`,La,[P(`div`,Ra,[P(`div`,{class:V([`flex-1 min-w-0 truncate text-sm font-medium`,[e.entry.status===`loading`?`op60`:``]])},q(e.entry.message),3),e.compact?A(``,!0):(G(),j(`span`,{key:0,class:`text-xs op40 flex-none`,title:new Date(e.entry.timestamp).toLocaleString()},q(U(n)),9,za)),Pe(t.$slots,`actions`)]),e.entry.description?(G(),j(`div`,Ba,q(e.entry.description),1)):A(``,!0),e.compact?A(``,!0):(G(),j(`div`,Va,[e.entry.category?(G(),F(Pa,{key:0,label:e.entry.category},null,8,[`label`])):A(``,!0),(G(!0),j(H,null,L(e.entry.labels,e=>(G(),F(Pa,{key:e,label:e},null,8,[`label`]))),128))]))])]))}}),Ua={key:0,class:`fixed bottom-4 right-4 z-dock-toast flex flex-col gap-2 pointer-events-auto w-72`},Wa=[`onClick`],Ga=[`onClick`],Ka=[`onClick`],qa=J({__name:`ToastOverlay`,props:{context:{}},setup(e){let t=e;t.context&&Aa(t.context);let n=Ta();function r(e){Da(e),ja(e),t.context?.docks.switchEntry(y)}function i(e){e.kind===`activate`?t.context?.docks.switchEntry(e.activate.dockId):e.kind===`command`&&t.context?.commands.execute(e.command.id,...e.command.params??[])}return(e,t)=>U(n).length>0?(G(),j(`div`,Ua,[I(at,{"enter-active-class":`transition-all duration-300 ease-out`,"leave-active-class":`transition-all duration-200 ease-in`,"enter-from-class":`opacity-0 translate-x-4`,"leave-to-class":`opacity-0 translate-x-4`},{default:z(()=>[(G(!0),j(H,null,L(U(n),e=>(G(),j(`div`,{key:e.id,class:`bg-toast-glass border color-base border-base shadow-xl cursor-pointer transition-colors rounded`,onClick:t=>r(e.id)},[I(Ha,{entry:e.entry,compact:``,class:`px-3 py-2.5`},{actions:z(()=>[(G(!0),j(H,null,L(e.entry.actions,e=>(G(),j(`button`,{key:e.id,class:`flex-none text-xs px-1.5 py-0.5 rounded border border-base op70 hover:op100 hover:bg-active transition`,onClick:Te(t=>i(e),[`stop`])},q(e.label),9,Ga))),128)),P(`button`,{class:`flex-none op30 hover:op100 p-0.5 rounded hover:bg-active transition`,onClick:Te(t=>U(Da)(e.id),[`stop`])},[...t[0]||=[P(`div`,{class:`i-ph-x w-3 h-3`},null,-1)]],8,Ka)]),_:2},1032,[`entry`])],8,Wa))),128))]),_:1})])):A(``,!0)}}),Ja=100,Ya=10,Xa=8;function Za(e,t,n){return Math.min(Math.max(e,t),Math.max(n,t))}function Qa({rect:e,anchorRight:t,anchorBottom:n,vw:r,vh:i}){return e.leftr-Ja?`left`:e.topi-Ja?`top`:`bottom`}function $a(e,t){let{rect:n,anchorRight:r,anchorBottom:i,vw:a,vh:o,gap:s,panelWidth:c,panelHeight:l}=t;return e===`bottom`&&i+s+l>o-Xa&&n.top-s-l>=Xa?`top`:e===`top`&&n.top-s-la-Xa&&n.left-s-c>=Xa?`left`:e===`left`&&n.left-s-ce[t]!==`none`)||/paint|layout|strict|content/.test(e.contain))&&(t=n)}return t?.parentElement??void 0}var io=J({name:`FloatingPopover`,props:{item:{type:Object,required:!1},dismissOnClickOutside:{type:Boolean,default:!0},panelClass:{type:[String,Array],required:!1},ignore:{type:Array,required:!1}},emits:[`dismiss`],setup(e,{emit:t}){let n=K(`panel`),r=B(e.item?.el),i=B(0),a=B();function o(e){a.value=e?ro(e):void 0}let s=nt({width:0,height:0}),c=B(!1);function l(){if(!e.item||!n.value)return;let{width:t,height:r}=n.value.getBoundingClientRect();(Math.abs(t-s.width)>.5||Math.abs(r-s.height)>.5)&&(s.width=t,s.height=r),Ae(()=>{c.value=!0})}Me(()=>{o(e.item?.el),l()}),we(l),Fe(window,`resize`,()=>{r.value&&i.value++}),Fe(window,`scroll`,()=>{r.value&&i.value++},{capture:!0,passive:!0});let u=st(()=>{e.item?.el??(r.value=void 0,s.width=0,s.height=0,c.value=!1)},800);e.dismissOnClickOutside&&We(n,()=>{t(`dismiss`)},{ignore:e.ignore}),Xe(()=>e.item,e=>{e?(r.value===e.el?i.value++:r.value=e.el,o(e.el)):u()});let d,f={},p=e=>a.value?je(Ke,{to:a.value},[e]):e;return()=>{if(i.value,!r.value)return null;let t=c.value?`transition-all duration-300`:`transition-opacity duration-300`;if(!e.item)return p(je(`div`,{ref:`panel`,class:[`fixed z-floating-tooltip text-xs ${t} w-max bg-glass:80 color-base border border-base rounded px2 p1`,`op0 pointer-events-none`,e.panelClass],style:f},d));let{style:n}=to({rect:r.value.getBoundingClientRect(),viewportWidth:window.innerWidth,viewportHeight:window.innerHeight,panelWidth:s.width,panelHeight:s.height,gap:e.item.gap,placement:e.item.placement});f=n;let a=(typeof e.item?.content==`string`?je(`span`,e.item?.content):e.item?.content())??d;return d=a,p(je(`div`,{ref:`panel`,class:[`fixed z-floating-tooltip text-xs ${t} w-max bg-glass:80 color-base border border-base rounded px2 p1`,e.item?`op100`:`op0 pointer-events-none`,e.panelClass],style:n},a))}}}),ao=J({__name:`FloatingElements`,setup(e){let t=de(),n=E(),r=T(),i=ne(),a=ae(),o=b();return Fe(window,`keydown`,e=>{e.key===`Escape`&&a.value&&le(null)}),(e,s)=>(G(),j(H,null,[I(U(io),{item:U(n),onDismiss:s[0]||=()=>U(D)(null)},null,8,[`item`]),I(U(io),{item:U(r),onDismiss:s[1]||=()=>U(O)(null)},null,8,[`item`]),I(U(io),{item:U(i),onDismiss:s[2]||=()=>U(S)(null)},null,8,[`item`]),I(U(io),{item:U(a),onDismiss:s[3]||=()=>U(le)(null)},null,8,[`item`]),I(U(io),{item:U(o),onDismiss:s[4]||=()=>U(C)(null)},null,8,[`item`]),I(U(io),{item:U(t)},null,8,[`item`])],64))}}),oo=[`aria-label`],so=[`value`,`autofocus`,`aria-label`,`aria-invalid`,`disabled`,`onInput`,`onKeydown`,`onPaste`],co=J({__name:`OtpInput`,props:{modelValue:{},length:{default:6},disabled:{type:Boolean,default:!1},invalid:{type:Boolean,default:!1},autofocus:{type:Boolean,default:!0},label:{default:`One-time authorization code`}},emits:[`update:modelValue`,`complete`],setup(e,{expose:t,emit:n}){let r=e,i=n,a=B([]),o=N(()=>Array.from({length:r.length},(e,t)=>r.modelValue[t]??``));function s(e,t){e&&(a.value[t]=e)}function c(e){let t=Math.max(0,Math.min(r.length-1,e));Ae(()=>{let e=a.value[t];e?.focus(),e?.select()})}function l(e){let t=e.replace(/\D/g,``).slice(0,r.length);t!==r.modelValue&&i(`update:modelValue`,t),t.length===r.length&&i(`complete`,t)}function u(e,t){let n=o.value.slice(),i=e;for(let e of t){if(i>=r.length)break;n[i]=e,i++}return l(n.join(``)),i}function d(e,t){let n=t.target.value.replace(/\D/g,``);if(!n){let t=o.value.slice();t[e]=``,l(t.join(``));return}c(u(e,n))}function f(e,t){switch(t.key){case`Backspace`:{t.preventDefault();let n=o.value.slice();n[e]?(n[e]=``,l(n.join(``))):e>0&&(n[e-1]=``,l(n.join(``)),c(e-1));break}case`Delete`:{t.preventDefault();let n=o.value.slice();n[e]=``,l(n.join(``));break}case`ArrowLeft`:t.preventDefault(),c(e-1);break;case`ArrowRight`:t.preventDefault(),c(e+1);break;case`Home`:t.preventDefault(),c(0);break;case`End`:t.preventDefault(),c(r.length-1)}}function p(e,t){t.preventDefault();let n=(t.clipboardData?.getData(`text`)??``).replace(/\D/g,``);n&&c(u(e,n.slice(0,r.length-e)))}function m(e){e.target.select()}function h(e){return r.invalid?`border-red-500/70 text-red-500`:o.value[e]?`border-primary-400/60 dark:border-primary-400/50`:`border-base`}return t({focus(){let e=o.value.findIndex(e=>!e);c(e===-1?r.length-1:e)}}),(t,n)=>(G(),j(`div`,{role:`group`,"aria-label":e.label,class:V([`flex items-center justify-center gap-2 sm:gap-2.5`,e.invalid?`devframes-shake`:``])},[(G(!0),j(H,null,L(o.value,(t,n)=>(G(),j(`input`,{key:n,ref_for:!0,ref:e=>s(e,n),value:t,type:`text`,inputmode:`numeric`,autocomplete:`one-time-code`,"data-1p-ignore":``,"data-lpignore":`true`,"data-bwignore":`true`,"data-form-type":`other`,autofocus:e.autofocus&&n===0,"aria-label":`Digit ${n+1} of ${e.length}`,"aria-invalid":e.invalid||void 0,disabled:e.disabled,maxlength:`1`,class:V([`w-11 h-14 text-center text-2xl font-mono tabular-nums font-medium rounded-xl border bg-base color-base caret-primary-500 outline-none transition-all duration-150 focus:border-primary-500 focus:ring-3 focus:ring-primary-500/25 disabled:op40 disabled:pointer-events-none`,h(n)]),onInput:e=>d(n,e),onKeydown:e=>f(n,e),onPaste:e=>p(n,e),onFocus:m},null,42,so))),128))],10,oo))}}),lo={class:`w-full h-full flex items-center justify-center p8 color-base overflow-auto`},uo={class:`w-full max-w-108 flex flex-col items-center text-center`},fo={class:`relative flex items-center justify-center`},po={class:`text-2xl font-bold tracking-tight`},mo={class:`mt2 text-sm op-fade leading-relaxed max-w-92`},ho={class:`mt6 w-full rounded-lg border border-base bg-base/60 shadow-sm p6 flex flex-col items-center gap-4`},go=6,_o=J({__name:`ViewBuiltinClientAuthNotice`,props:{context:{}},setup(e){let t=e,n=qe(),r=B(``),i=B(``),a=B(!1),o=K(`otp`),s=!1;Xe(r,()=>{if(s){s=!1;return}i.value&&=``});async function c(){let e=r.value;if(!(e.length(G(),j(`div`,lo,[P(`div`,uo,[P(`div`,fo,[t[1]||=P(`div`,{class:`absolute w-24 h-24 rounded-full bg-primary-500/20 blur-2xl`,"aria-hidden":`true`},null,-1),I(ur,{class:`relative w-16 h-16`})]),P(`h1`,po,` Authorize `+q(U(n).productName),1),P(`p`,mo,q(U(n).productName)+` can access your server, read your filesystem, and run commands. Confirm it's you before continuing. `,1),P(`div`,ho,[t[2]||=P(`div`,{class:`flex flex-col items-center gap-1`},[P(`p`,{class:`text-sm op-fade`},[M(` Find the `),P(`span`,{class:`font-mono text-primary-600 dark:text-primary-300`},`6-digit code`),M(` printed in your terminal. `)])],-1),P(`form`,{class:`flex flex-col items-center gap-4 w-full`,autocomplete:`off`,onSubmit:Te(c,[`prevent`])},[I(co,{ref_key:`otp`,ref:o,modelValue:r.value,"onUpdate:modelValue":t[0]||=e=>r.value=e,length:go,invalid:!!i.value,disabled:a.value,label:`Enter your one-time authorization code`,onComplete:c},null,8,[`modelValue`,`invalid`,`disabled`]),I(ma,{type:`submit`,variant:`primary`,icon:`i-ph-shield-check-duotone`,class:`w-full justify-center text-base py-2! disabled:saturate-0`,loading:a.value,disabled:r.value.length[M(q(a.value?`Authorizing`:`Authorize`),1)]),_:1},8,[`loading`,`disabled`]),i.value||a.value?(G(),j(`p`,{key:0,class:V([`text-sm min-h-5 transition-colors`,i.value?`text-red-500`:`op-mute`]),role:`alert`,"aria-live":`assertive`},[i.value?(G(),j(H,{key:0},[M(q(i.value),1)],64)):a.value?(G(),j(H,{key:1},[M(` Authorizing... `)],64)):A(``,!0)],2)):A(``,!0)],32)]),t[3]||=P(`p`,{class:`mt4 text-xs op-mute max-w-92 leading-relaxed`},` You can also use the magic link to authorize automatically. `,-1)])]))}}),vo={class:`flex flex-col gap-6`},yo={class:`flex items-center gap-3 cursor-pointer group`},bo=J({__name:`SettingsAdvanced`,props:{context:{}},setup(e){let t=e,n=oe(t.context),r=t.context.docks.settings,i=qe(),a=ga();async function o(){await a({title:`Reset All Settings`,message:`Reset all settings to defaults? This includes appearance, docks, and shortcuts.`})&&r.mutate(()=>be())}async function s(){await a({title:`Reset Keyboard Shortcuts`,message:`Reset all keyboard shortcuts to defaults?`})&&r.mutate(e=>{e.commandShortcuts={}})}async function c(){await a({title:`Reset Dock Settings`,message:`Reset dock visibility, order, and pinning to defaults?`})&&r.mutate(e=>{let t=be();e.docksHidden=t.docksHidden,e.docksCategoriesHidden=t.docksCategoriesHidden,e.docksCustomOrder=t.docksCustomOrder,e.docksPinned=t.docksPinned})}async function l(){await a({title:`Revoke Authorization`,message:`Revoke this browser's access to ${i.value.productName}? You'll need to authorize again with a new code to reconnect.`})&&await t.context.rpc.call(`devframe:auth:revoke`)}return(e,t)=>(G(),j(`div`,vo,[P(`label`,yo,[P(`button`,{class:V([`w-10 h-6 rounded-full transition-colors relative shrink-0`,U(n).showDevframeInspector?`bg-primary`:`bg-gray/30`]),onClick:t[0]||=e=>U(r).mutate(e=>{e.showDevframeInspector=!U(n).showDevframeInspector})},[P(`div`,{class:V([`absolute top-1 w-4 h-4 rounded-full bg-white shadow transition-transform`,U(n).showDevframeInspector?`translate-x-5`:`translate-x-1`])},null,2)],2),t[1]||=P(`div`,{class:`flex flex-col`},[P(`span`,{class:`text-sm`},`Show Devframe Inspector`),P(`span`,{class:`text-xs op50`},`Reveal the experimental Devframe Inspector dock, the devtools for the devtools`)],-1)]),t[10]||=P(`div`,{class:`border-t border-base`},null,-1),P(`div`,{class:`flex items-start gap-4`},[t[3]||=P(`div`,{class:`flex-1`},[P(`div`,{class:`text-sm`},` Reset Keyboard Shortcuts `),P(`div`,{class:`text-xs op50 mt-0.5`},` Remove all custom shortcut overrides and restore default keybindings `)],-1),P(`button`,{class:`px-4 py-2 rounded bg-orange/10 text-orange hover:bg-orange/20 transition-colors flex items-center gap-2 text-sm shrink-0`,onClick:s},[...t[2]||=[P(`div`,{class:`i-ph-keyboard-duotone w-4 h-4`},null,-1),M(` Reset Shortcuts `,-1)]])]),P(`div`,{class:`flex items-start gap-4`},[t[5]||=P(`div`,{class:`flex-1`},[P(`div`,{class:`text-sm`},` Reset Dock Settings `),P(`div`,{class:`text-xs op50 mt-0.5`},` Restore default dock visibility, order, and pinning `)],-1),P(`button`,{class:`px-4 py-2 rounded bg-orange/10 text-orange hover:bg-orange/20 transition-colors flex items-center gap-2 text-sm shrink-0`,onClick:c},[...t[4]||=[P(`div`,{class:`i-ph-layout-duotone w-4 h-4`},null,-1),M(` Reset Docks `,-1)]])]),P(`div`,{class:`border-t border-base pt-6`},[P(`div`,{class:`flex items-start gap-4`},[t[7]||=P(`div`,{class:`flex-1`},[P(`div`,{class:`text-sm`},` Reset All Settings `),P(`div`,{class:`text-xs op50 mt-0.5`},` Reset everything to defaults including appearance, docks, and shortcuts `)],-1),P(`button`,{class:`px-4 py-2 rounded bg-red/10 text-red hover:bg-red/20 transition-colors flex items-center gap-2 text-sm shrink-0`,onClick:o},[...t[6]||=[P(`div`,{class:`i-ph-arrow-counter-clockwise w-4 h-4`},null,-1),M(` Reset All `,-1)]])])]),P(`div`,{class:`border-t border-base pt-6`},[P(`div`,{class:`flex items-start gap-4`},[t[9]||=P(`div`,{class:`flex-1`},[P(`div`,{class:`text-sm`},` Revoke Authorization `),P(`div`,{class:`text-xs op50 mt-0.5`},` De-authorize this browser and revoke its access token; you'll re-authorize with a new code `)],-1),P(`button`,{class:`px-4 py-2 rounded bg-red/10 text-red hover:bg-red/20 transition-colors flex items-center gap-2 text-sm shrink-0`,onClick:l},[...t[8]||=[P(`div`,{class:`i-ph-sign-out-duotone w-4 h-4`},null,-1),M(` Revoke Access `,-1)]])])])]))}}),xo={class:`flex flex-col gap-4`},So={class:`flex flex-col gap-2`},Co={class:`flex flex-col`},wo={class:`text-xs op50`},To={class:`flex items-center gap-1 bg-gray/10 rounded-lg p1 w-fit`},Eo=[`onClick`],Do={key:0,class:`flex flex-col gap-2`},Oo={class:`flex flex-col`},ko={class:`text-xs op50`},Ao={class:`flex items-center gap-1 bg-gray/10 rounded-lg p1 w-fit`},jo=[`onClick`],Mo={class:`flex items-center gap-3 cursor-pointer group`},No={class:`flex items-center gap-3 cursor-pointer group`},Po={class:`flex flex-col`},Fo={class:`text-xs op50`},Io={class:`flex items-center gap-3 cursor-pointer group`},Lo=J({__name:`SettingsAppearance`,props:{context:{}},setup(e){let t=e,n=oe(t.context),r=t.context.docks.settings,i=qe(),a=t.context.panel.store,o=t.context.clientType===`embedded`,s=ie(),c=N(()=>{let e=[{value:`float`,label:`Float`,icon:`i-ph-cards-three-duotone`},{value:`edge`,label:`Edge`,icon:`i-ph-square-half-bottom-duotone`}];return ue()&&e.push({value:`popup`,label:`Popup`,icon:`i-ph-arrow-square-out-duotone`}),e}),l=N(()=>a.mode),u=[{value:`auto`,label:`Auto`,icon:`i-ph-laptop-duotone`},{value:`light`,label:`Light`,icon:`i-ph-sun-duotone`},{value:`dark`,label:`Dark`,icon:`i-ph-moon-duotone`}];function d(e){e===`popup`?se(t.context):a.mode=e}return(e,t)=>(G(),j(`div`,xo,[P(`div`,So,[P(`div`,Co,[t[3]||=P(`span`,{class:`text-sm`},`Color mode`,-1),P(`span`,wo,`Theme for `+q(U(i).productName)+` and its inner panels`,1)]),P(`div`,To,[(G(),j(H,null,L(u,e=>P(`button`,{key:e.value,class:V([`flex items-center gap-1.5 px3 py1.5 rounded-md text-sm transition-all`,U(Re)===e.value?`bg-base shadow text-primary font-medium`:`op60 hover:op100 hover:bg-gray/10`]),onClick:t=>U(Qe)(e.value)},[P(`div`,{class:V([e.icon,`w-4 h-4`])},null,2),M(` `+q(e.label),1)],10,Eo)),64))])]),o&&!U(s)?(G(),j(`div`,Do,[P(`div`,Oo,[t[4]||=P(`span`,{class:`text-sm`},`Dock mode`,-1),P(`span`,ko,`How the `+q(U(i).productName)+` panel is displayed`,1)]),P(`div`,Ao,[(G(!0),j(H,null,L(c.value,e=>(G(),j(`button`,{key:e.value,class:V([`flex items-center gap-1.5 px3 py1.5 rounded-md text-sm transition-all`,l.value===e.value?`bg-base shadow text-primary font-medium`:`op60 hover:op100 hover:bg-gray/10`]),onClick:t=>d(e.value)},[P(`div`,{class:V([e.icon,`w-4 h-4`])},null,2),M(` `+q(e.label),1)],10,jo))),128))])])):A(``,!0),P(`label`,Mo,[P(`button`,{class:V([`w-10 h-6 rounded-full transition-colors relative shrink-0`,U(n).showIframeAddressBar?`bg-primary`:`bg-gray/30`]),onClick:t[0]||=e=>U(r).mutate(e=>{e.showIframeAddressBar=!U(n).showIframeAddressBar})},[P(`div`,{class:V([`absolute top-1 w-4 h-4 rounded-full bg-white shadow transition-transform`,U(n).showIframeAddressBar?`translate-x-5`:`translate-x-1`])},null,2)],2),t[5]||=P(`div`,{class:`flex flex-col`},[P(`span`,{class:`text-sm`},`Always show iframe address bars`),P(`span`,{class:`text-xs op50`},`Show the address bar for every iframe dock.`)],-1)]),P(`label`,No,[P(`button`,{class:V([`w-10 h-6 rounded-full transition-colors relative shrink-0`,U(n).closeOnOutsideClick?`bg-primary`:`bg-gray/30`]),onClick:t[1]||=e=>U(r).mutate(e=>{e.closeOnOutsideClick=!U(n).closeOnOutsideClick})},[P(`div`,{class:V([`absolute top-1 w-4 h-4 rounded-full bg-white shadow transition-transform`,U(n).closeOnOutsideClick?`translate-x-5`:`translate-x-1`])},null,2)],2),P(`div`,Po,[t[6]||=P(`span`,{class:`text-sm`},`Close panel on outside click`,-1),P(`span`,Fo,`Close the `+q(U(i).productName)+` panel when clicking outside of it (embedded mode only)`,1)])]),P(`label`,Io,[P(`button`,{class:V([`w-10 h-6 rounded-full transition-colors relative shrink-0`,U(n).autoCollapseEdgeToolbar?`bg-primary`:`bg-gray/30`]),onClick:t[2]||=e=>U(r).mutate(e=>{e.autoCollapseEdgeToolbar=!U(n).autoCollapseEdgeToolbar})},[P(`div`,{class:V([`absolute top-1 w-4 h-4 rounded-full bg-white shadow transition-transform`,U(n).autoCollapseEdgeToolbar?`translate-x-5`:`translate-x-1`])},null,2)],2),t[7]||=P(`div`,{class:`flex flex-col`},[P(`span`,{class:`text-sm`},`Auto-collapse edge toolbar`),P(`span`,{class:`text-xs op50`},`Shrink the edge-docked toolbar to a small handle when idle and closed (edge mode only)`)],-1)])]))}}),Ro={ref:`sortContainer`,class:`flex flex-col gap-4`},zo={class:`flex items-center gap-2 px-4 py-3 bg-gray/5 cursor-pointer select-none border-b border-base`},Bo=[`disabled`,`onClick`],Vo={class:`font-medium capitalize`},Ho={class:`text-xs op40`},Uo=[`onClick`],Wo=[`data-dock-id`,`data-container`],Go=[`disabled`,`title`,`onClick`],Ko={key:0,class:`i-ph-check-bold text-xs`},qo={class:`flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity`},Jo=[`onClick`],Yo=[`onClick`],Xo=[`title`,`onClick`],Zo={class:`bg-gray/3 flex flex-col w-full flex-auto`},Qo={key:0,class:`px-2 py-2 text-xs op40 italic border-b border-base border-t-0`},$o={key:0,class:`flex items-center gap-2 px-2 py-1.5 bg-gray/5 border-b border-base border-t-0`},es={class:`text-xs uppercase tracking-wide op50`},ts=[`onClick`],ns=[`data-dock-id`,`data-container`],rs=[`title`,`onClick`],is={key:0,class:`i-ph-check-bold text-xs`},as={class:`flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity`},os=[`onClick`],ss=[`onClick`],cs=[`title`,`onClick`],ls=`:::`,us=4,ds=J({__name:`SettingsDocks`,props:{context:{}},setup(e){let t=e,r=oe(t.context),i=t.context.docks.settings,a=N(()=>s(t.context.docks.entries,r.value,{includeHidden:!0,collapseGroups:!0}));function o(e){return n(t.context.docks.entries,e,r.value,{includeHidden:!0})}function c(e){return v(t.context.docks.entries,e,r.value,{includeHidden:!0})}let l=e=>`cat:${e}`,u=(e,t)=>`grp:${e}${ls}${t}`;function d(e){let t=e.slice(4),n=t.indexOf(ls);return{groupId:t.slice(0,n),subcategory:t.slice(n+3)}}function f(e){if(e.startsWith(`grp:`)){let{groupId:t,subcategory:n}=d(e);return c(t).find(([e])=>e===n)?.[1]??[]}let t=e.slice(4);return a.value.find(([e])=>e===t)?.[1]??[]}function p(e){let n={...r.value,docksCustomOrder:{}};if(e.startsWith(`grp:`)){let{groupId:r,subcategory:i}=d(e);return v(t.context.docks.entries,r,n,{includeHidden:!0}).find(([e])=>e===i)?.[1]??[]}let i=e.slice(4);return s(t.context.docks.entries,n,{includeHidden:!0,collapseGroups:!0}).find(([e])=>e===i)?.[1]??[]}let h=K(`sortContainer`),g=new Map,y=B(null),b=B(null),x=B(null),S=B(!1),C=0;function w(e){let t=e.target;for(;t&&t!==h.value;){if(t.dataset.dockId)return{dockId:t.dataset.dockId,container:t.dataset.container??null};t=t.parentElement}return{dockId:null,container:null}}function T(e){for(;e&&e!==h.value;){if(e.tagName===`BUTTON`||e.tagName===`A`||e.tagName===`INPUT`)return!0;e=e.parentElement}return!1}function E(e,t){let n=p(e).map(e=>e.id),r=t.length===n.length&&t.every((e,t)=>e.id===n[t]);i.mutate(e=>{t.forEach((t,n)=>{r?delete e.docksCustomOrder[t.id]:e.docksCustomOrder[t.id]=n})})}He(h,{draggingElement:()=>h.value?.ownerDocument?.defaultView??window,onStart(e,t){if(t.button!==0||T(t.target))return!1;let{dockId:n,container:r}=w(t);if(!n||!r)return!1;y.value=n,b.value=r,S.value=!1,C=t.clientY},onMove(e,t){if(!y.value)return;if(!S.value){if(Math.abs(t.clientY-C)=a.top&&t.clientY<=a.bottom){n=e;break}}x.value=n},onEnd(){if(y.value&&S.value&&x.value&&b.value){let e=[...f(b.value)],t=e.findIndex(e=>e.id===y.value),n=e.findIndex(e=>e.id===x.value);t!==-1&&n!==-1&&(e.splice(n,0,e.splice(t,1)[0]),E(b.value,e))}y.value=null,b.value=null,x.value=null,S.value=!1}});function ee(e,t,n){e?g.set(t,{el:e,container:n}):g.delete(t)}function te(e,t){if(e===`~settings`)return;let n=r.value.docksHidden.includes(e);t??n?i.mutate(t=>{t.docksHidden=t.docksHidden.filter(t=>t!==e)}):i.mutate(t=>{t.docksHidden=[...t.docksHidden,e]})}function D(e,t){if(!_(e))return;let n=r.value.docksCategoriesHidden.includes(e);t??n?i.mutate(t=>{t.docksCategoriesHidden=t.docksCategoriesHidden.filter(t=>t!==e)}):i.mutate(t=>{t.docksCategoriesHidden=[...t.docksCategoriesHidden,e]})}function ne(e){r.value.docksPinned.includes(e)?i.mutate(t=>{t.docksPinned=t.docksPinned.filter(t=>t!==e)}):i.mutate(t=>{t.docksPinned=[...t.docksPinned,e]})}function re(e,t,n){let r=[...f(e)],i=r.findIndex(e=>e.id===t),a=i+n;a<0||a>=r.length||(r.splice(a,0,r.splice(i,1)[0]),E(e,r))}function ie(e){let t=f(e),n=t.map(e=>e.id);if(e.startsWith(`cat:`))for(let e of t)e.type===`group`&&n.push(...o(e.id).map(e=>e.id));return n}function ae(e){let t=f(e).map(e=>e.id),n=p(e).map(e=>e.id);return t.length!==n.length||t.some((e,t)=>e!==n[t])?!0:e.startsWith(`cat:`)?f(e).some(e=>e.type===`group`&&c(e.id).some(([t])=>ae(u(e.id,t)))):!1}function se(e){let t=ie(e);i.mutate(e=>{t.forEach(t=>{delete e.docksCustomOrder[t]})})}return(e,t)=>(G(),j(H,null,[t[11]||=P(`p`,{class:`text-sm op50 mb-4`},` Manage visibility and order of dock entries. Hidden entries will not appear in the dock bar. `,-1),P(`div`,Ro,[(G(!0),j(H,null,L(a.value,([e,n])=>(G(),j(`div`,{key:e,class:V([`border border-base rounded-lg overflow-hidden transition-opacity`,U(r).docksCategoriesHidden.includes(e)?`op40`:``])},[P(`div`,zo,[P(`button`,{class:V([`w-5 h-5 flex items-center justify-center rounded transition-colors`,[U(_)(e)?U(r).docksCategoriesHidden.includes(e)?`bg-gray/20`:`bg-primary/20 text-primary`:`bg-gray/20 cursor-not-allowed op50`]]),disabled:!U(_)(e),onClick:t=>D(e)},[P(`div`,{class:V([`transition-transform`,U(r).docksCategoriesHidden.includes(e)?`i-ph-eye-slash text-sm op50`:`i-ph-check-bold text-xs`])},null,2)],10,Bo),P(`span`,Vo,q(U(m)(e)),1),P(`span`,Ho,`(`+q(n.length)+`)`,1),t[1]||=P(`span`,{class:`flex-auto`},null,-1),ae(l(e))?(G(),j(`button`,{key:0,class:`w-6 h-6 flex items-center justify-center rounded hover:bg-gray/20 transition-colors`,title:`Reset custom order`,onClick:t=>se(l(e))},[...t[0]||=[P(`div`,{class:`i-ph-arrows-counter-clockwise-duotone text-sm op60`},null,-1)]],8,Uo)):A(``,!0)]),P(`div`,null,[(G(!0),j(H,null,L(n,(i,a)=>(G(),j(H,{key:i.id},[P(`div`,{ref_for:!0,ref:t=>ee(t,i.id,l(e)),"data-dock-id":i.id,"data-container":l(e),class:V([`flex items-center gap-3 px-2 py-2.5 hover:bg-gray/5 transition-all group border-b border-base border-t-0`,[U(r).docksHidden.includes(i.id)?`op40`:``,S.value&&y.value===i.id?`op30 bg-gray/10`:``,x.value===i.id?`ring-1.5 ring-purple/50 rounded`:``,S.value?`select-none`:``]])},[P(`div`,{class:`i-ph-dots-six-vertical w-4 h-4 shrink-0 op25 group-hover:op50 transition-opacity cursor-grab`,style:R(S.value&&y.value===i.id?`cursor: grabbing`:``)},null,4),P(`button`,{class:V([`w-6 h-6 flex items-center justify-center rounded border border-transparent hover:border-base transition-colors shrink-0`,i.id===`~settings`?`cursor-not-allowed op50`:U(r).docksHidden.includes(i.id)?`op50`:``]),disabled:i.id===`~settings`,title:i.id===`~settings`?`Always visible`:U(r).docksHidden.includes(i.id)?`Show`:`Hide`,onClick:e=>te(i.id)},[P(`div`,{class:V([`w-4 h-4 rounded flex items-center justify-center transition-colors`,i.id===`~settings`||U(r).docksHidden.includes(i.id)?`bg-gray/30`:`bg-primary/20 text-primary`])},[U(r).docksHidden.includes(i.id)?A(``,!0):(G(),j(`div`,Ko))],2)],10,Go),I(Ui,{icon:i.icon,title:i.title,class:V([`w-5 h-5 shrink-0`,U(r).docksHidden.includes(i.id)?`saturate-0`:``])},null,8,[`icon`,`title`,`class`]),P(`span`,{class:V([`truncate`,U(r).docksHidden.includes(i.id)?`line-through op60`:``])},q(i.title),3),i.type===`action`?(G(),F(Pa,{key:0,label:`Action`,class:`flex-none text-xs`})):i.type===`group`?(G(),F(Pa,{key:1,label:`Group`,class:`flex-none text-xs`})):A(``,!0),t[4]||=P(`div`,{class:`flex flex-auto`},null,-1),P(`div`,qo,[a>0?(G(),j(`button`,{key:0,class:`w-6 h-6 flex items-center justify-center rounded hover:bg-gray/20 transition-colors`,title:`Move up (higher priority)`,onClick:t=>re(l(e),i.id,-1)},[...t[2]||=[P(`div`,{class:`i-ph-caret-up text-sm op60`},null,-1)]],8,Jo)):A(``,!0),are(l(e),i.id,1)},[...t[3]||=[P(`div`,{class:`i-ph-caret-down text-sm op60`},null,-1)]],8,Yo)):A(``,!0)]),P(`button`,{class:V([`w-7 h-7 flex items-center justify-center rounded hover:bg-gray/20 transition-colors shrink-0`,U(r).docksPinned.includes(i.id)?`text-amber`:`op40 hover:op70`]),title:U(r).docksPinned.includes(i.id)?`Unpin`:`Pin`,onClick:e=>ne(i.id)},[P(`div`,{class:V([U(r).docksPinned.includes(i.id)?`i-ph-push-pin-fill rotate--45`:`i-ph-push-pin`,`color-base`])},null,2)],10,Xo)],10,Wo),i.type===`group`?(G(),j(`div`,{key:0,class:V([`flex`,U(r).docksHidden.includes(i.id)?`op50`:``])},[t[10]||=P(`div`,{class:`border-base border-r border-b w-6 flex-none`},null,-1),P(`div`,Zo,[o(i.id).length===0?(G(),j(`div`,Qo,` No tools in this group yet `)):A(``,!0),(G(!0),j(H,null,L(c(i.id),([e,n])=>(G(),j(H,{key:e},[c(i.id).length>1?(G(),j(`div`,$o,[P(`span`,es,q(U(m)(e)),1),t[6]||=P(`span`,{class:`flex-auto`},null,-1),ae(u(i.id,e))?(G(),j(`button`,{key:0,class:`w-5 h-5 flex items-center justify-center rounded hover:bg-gray/20 transition-colors`,title:`Reset custom order`,onClick:t=>se(u(i.id,e))},[...t[5]||=[P(`div`,{class:`i-ph-arrows-counter-clockwise-duotone text-xs op60`},null,-1)]],8,ts)):A(``,!0)])):A(``,!0),(G(!0),j(H,null,L(n,(a,o)=>(G(),j(`div`,{key:a.id,ref_for:!0,ref:t=>ee(t,a.id,u(i.id,e)),"data-dock-id":a.id,"data-container":u(i.id,e),class:V([`flex items-center gap-3 px-2 py-2 hover:bg-gray/5 transition-all group border-b border-base border-t-0`,[U(r).docksHidden.includes(a.id)?`op40`:``,S.value&&y.value===a.id?`op30 bg-gray/10`:``,x.value===a.id?`ring-1.5 ring-purple/50 rounded`:``,S.value?`select-none`:``]])},[P(`div`,{class:`i-ph-dots-six-vertical w-4 h-4 shrink-0 op25 group-hover:op50 transition-opacity cursor-grab`,style:R(S.value&&y.value===a.id?`cursor: grabbing`:``)},null,4),P(`button`,{class:V([`w-6 h-6 flex items-center justify-center rounded border border-transparent hover:border-base transition-colors shrink-0`,U(r).docksHidden.includes(a.id)?`op50`:``]),title:U(r).docksHidden.includes(a.id)?`Show`:`Hide`,onClick:e=>te(a.id)},[P(`div`,{class:V([`w-4 h-4 rounded flex items-center justify-center transition-colors`,U(r).docksHidden.includes(a.id)?`bg-gray/30`:`bg-primary/20 text-primary`])},[U(r).docksHidden.includes(a.id)?A(``,!0):(G(),j(`div`,is))],2)],10,rs),I(Ui,{icon:a.icon,title:a.title,class:V([`w-5 h-5 shrink-0`,U(r).docksHidden.includes(a.id)?`saturate-0`:``])},null,8,[`icon`,`title`,`class`]),P(`span`,{class:V([`truncate text-sm`,U(r).docksHidden.includes(a.id)?`line-through op60`:``])},q(a.title),3),a.type===`action`?(G(),F(Pa,{key:0,label:`Action`,class:`flex-none text-xs`})):A(``,!0),t[9]||=P(`div`,{class:`flex flex-auto`},null,-1),P(`div`,as,[o>0?(G(),j(`button`,{key:0,class:`w-6 h-6 flex items-center justify-center rounded hover:bg-gray/20 transition-colors`,title:`Move up (higher priority)`,onClick:t=>re(u(i.id,e),a.id,-1)},[...t[7]||=[P(`div`,{class:`i-ph-caret-up text-sm op60`},null,-1)]],8,os)):A(``,!0),ore(u(i.id,e),a.id,1)},[...t[8]||=[P(`div`,{class:`i-ph-caret-down text-sm op60`},null,-1)]],8,ss)):A(``,!0)]),P(`button`,{class:V([`w-7 h-7 flex items-center justify-center rounded hover:bg-gray/20 transition-colors shrink-0`,U(r).docksPinned.includes(a.id)?`text-amber`:`op40 hover:op70`]),title:U(r).docksPinned.includes(a.id)?`Unpin`:`Pin`,onClick:e=>ne(a.id)},[P(`div`,{class:V([U(r).docksPinned.includes(a.id)?`i-ph-push-pin-fill rotate--45`:`i-ph-push-pin`,`color-base`])},null,2)],10,cs)],10,ns))),128))],64))),128))])],2)):A(``,!0)],64))),128))])],2))),128))],512)],64))}}),fs={class:`mb-3`},ps={class:`border border-base rounded-lg overflow-hidden`},ms={class:`flex-1 min-w-0`},hs={class:`flex items-center gap-1.5`},gs={class:`truncate text-sm`},_s={key:0,class:`text-[10px] px-1.5 py-0.5 rounded bg-blue/10 text-blue shrink-0`},vs={key:0,class:`text-xs op40 truncate mt-0.5`},ys={key:2,class:`flex items-center gap-1.5 shrink-0`},bs=[`onClick`],xs=[`onClick`],Ss=[`onClick`],Cs=[`onClick`],ws={key:0,class:`py-6 text-center text-sm op40`},Ts={class:`relative bg-base border border-base rounded-lg shadow-xl w-96 p-5`},Es={class:`mb-4`},Ds=[`value`],Os={class:`flex items-center gap-2 mb-4`},ks={key:0,class:`mb-4 flex flex-col gap-1.5`},As={class:`flex items-center justify-end gap-2`},js=[`disabled`],Ms=J({__name:`SettingsShortcuts`,props:{context:{}},setup(e){let n=e,r=n.context.commands,a=oe(n.context),o=N(()=>a.value.commandShortcuts??{}),s=B(``),c=N(()=>d(r.commands,{...n.context.when.context,dockOpen:!0,paletteOpen:!1})),l=N(()=>{let e=[];return f(c.value,(t,n)=>{let r=n.at(-1)?.title;e.push({command:t,...r?{parentTitle:r}:{},depth:n.length})}),e}),p=N(()=>{if(!s.value)return l.value;let e=s.value.toLowerCase();return l.value.filter(t=>t.command.title.toLowerCase().includes(e)||t.command.id.toLowerCase().includes(e)||t.command.description?.toLowerCase().includes(e))});function m(e){return r.getKeybindings(e)}function h(e){return e.depth>0?{marginLeft:`${e.depth*1.5}rem`}:{}}function g(e){return e.source===`server`||!!e.action}function _(e){return ee(o.value[e],v(e))}function v(e){return te(r.commands,e)?.keybindings??[]}function y(e){r.settings.mutate(t=>{v(e).length>0?t.commandShortcuts[e]=[]:delete t.commandShortcuts[e]})}function b(e){r.settings.mutate(t=>{delete t.commandShortcuts[e]})}let x=B(!1),S=B(null),C=B(!1),w=B(!1),T=B(!1),E=B(!1),D=B(``);function ne(e){let t=m(e);if(S.value=e,t.length>0){let e=t[0].key.split(`+`);C.value=e.includes(`Mod`),w.value=e.includes(`Ctrl`),T.value=e.includes(`Alt`),E.value=e.includes(`Shift`),D.value=e.filter(e=>![`Mod`,`Ctrl`,`Alt`,`Shift`,`Meta`].includes(e)).join(`+`)}else C.value=!1,w.value=!1,T.value=!1,E.value=!1,D.value=``;x.value=!0}function re(){x.value=!1,S.value=null}let ie=N(()=>{let e=[];return C.value&&e.push(`Mod`),w.value&&e.push(`Ctrl`),T.value&&e.push(`Alt`),E.value&&e.push(`Shift`),D.value&&e.push(D.value),e.join(`+`)}),ae=N(()=>{let e=[],n=ie.value;if(!n)return e;let r=C.value||w.value||T.value;!r&&D.value&&e.push(`Single key without modifiers may interfere with typing`),!r&&E.value&&D.value.length===1&&e.push(`Shift + letter may interfere with typing`);let a=i[n];if(a){let r=t(n).join(`+`);e.push(`Conflicts with browser shortcut: ${a} (${r})`)}if(S.value){for(let t of l.value)if(t.command.id!==S.value&&m(t.command.id).some(e=>e.key===n)){e.push(`Conflicts with "${t.command.title}"`);break}}return e}),se=N(()=>D.value.length>0);function ce(e){if(e.preventDefault(),e.stopPropagation(),[`Control`,`Meta`,`Alt`,`Shift`].includes(e.key))return;C.value=u?e.metaKey:e.ctrlKey,u&&e.ctrlKey&&(w.value=!0),T.value=e.altKey,E.value=e.shiftKey;let t=e.key;t.length===1&&(t=t.toUpperCase()),D.value=t}function le(){if(!S.value||!se.value)return;let e=[{key:ie.value}];ee(e,v(S.value))?r.settings.mutate(t=>{t.commandShortcuts[S.value]=e}):r.settings.mutate(e=>{delete e.commandShortcuts[S.value]}),re()}let ue=B(null);return Xe(x,async e=>{if(!e)return;await Ae();let t=ue.value?.ownerDocument??document,n=t.defaultView??window,r=e=>{if(e.key===`Escape`){let e=t.activeElement;(!e||!e.classList.contains(`shortcut-key-input`))&&(re(),n.removeEventListener(`keydown`,r))}};n.addEventListener(`keydown`,r)}),(e,n)=>(G(),j(H,null,[n[10]||=P(`p`,{class:`text-sm op50 mb-4`},` Customize keyboard shortcuts for commands. `,-1),P(`div`,fs,[Ze(P(`input`,{"onUpdate:modelValue":n[0]||=e=>s.value=e,class:`w-full bg-gray/10 rounded-lg px-3 py-2 text-sm outline-none focus:ring-1 focus:ring-primary/30`,placeholder:`Search commands...`},null,512),[[ke,s.value]])]),P(`div`,ps,[(G(!0),j(H,null,L(p.value,e=>(G(),j(`div`,{key:e.command.id,class:`flex items-center gap-3 px-4 py-2.5 hover:bg-gray/5 transition-colors border-b border-base last:border-b-0`},[e.command.icon?(G(),F(Ui,{key:0,icon:e.command.icon,class:`w-4 h-4 shrink-0 op60`,style:R(h(e))},null,8,[`icon`,`style`])):(G(),j(`div`,{key:1,style:R(h(e)),class:`w-4 h-4 shrink-0`},null,4)),P(`div`,ms,[P(`div`,hs,[P(`span`,gs,q(e.command.title),1),e.command.source===`server`?(G(),j(`span`,_s,`server`)):A(``,!0)]),e.command.description?(G(),j(`div`,vs,q(e.command.description),1)):A(``,!0)]),g(e.command)?(G(),j(`div`,ys,[m(e.command.id).length>0?(G(!0),j(H,{key:0},L(m(e.command.id),(t,n)=>(G(),j(`button`,{key:n,class:`flex items-center gap-0.5 hover:bg-gray/10 rounded px-1 py-0.5 transition-colors`,title:`Click to edit`,onClick:t=>ne(e.command.id)},[I(sr,{keys:t.key},null,8,[`keys`])],8,bs))),128)):(G(),j(`button`,{key:1,class:`text-xs op30 hover:op60 px-2 py-1 rounded hover:bg-gray/10 transition-colors`,onClick:t=>ne(e.command.id)},` Add shortcut `,8,xs)),m(e.command.id).length>0?(G(),j(`button`,{key:2,class:`w-5 h-5 flex items-center justify-center rounded hover:bg-gray/20 transition-colors op30 hover:op70`,title:`Clear shortcut`,onClick:t=>y(e.command.id)},[...n[5]||=[P(`div`,{class:`i-ph-x text-xs`},null,-1)]],8,Ss)):A(``,!0),_(e.command.id)?(G(),j(`button`,{key:3,class:`w-5 h-5 flex items-center justify-center rounded hover:bg-gray/20 transition-colors op30 hover:op70`,title:`Reset to default`,onClick:t=>b(e.command.id)},[...n[6]||=[P(`div`,{class:`i-ph-arrow-counter-clockwise text-xs`},null,-1)]],8,Cs)):A(``,!0)])):A(``,!0)]))),128)),p.value.length===0?(G(),j(`div`,ws,` No commands found `)):A(``,!0)]),x.value?(G(),j(`div`,{key:0,ref_key:`editorModal`,ref:ue,class:`fixed inset-0 z-command-palette flex items-center justify-center`},[P(`div`,{class:`absolute inset-0 bg-black/30`,onClick:re}),P(`div`,Ts,[n[9]||=P(`h3`,{class:`text-sm font-medium mb-4`},` Edit Shortcut `,-1),P(`div`,Es,[n[7]||=P(`label`,{class:`text-xs op50 mb-1.5 block`},`Press a key combination or toggle modifiers below`,-1),P(`input`,{class:`shortcut-key-input w-full bg-gray/10 border border-base rounded-lg px-3 py-2.5 text-center text-sm font-mono outline-none focus:border-primary/40`,value:ie.value?U(t)(ie.value).join(` + `):``,placeholder:`Press keys...`,readonly:``,onKeydown:ce},null,40,Ds)]),P(`div`,Os,[P(`button`,{class:V([`px-3 py-1.5 rounded-md text-xs font-mono border transition-colors`,C.value?`bg-primary/15 border-primary/30 text-primary`:`border-base op50 hover:op80`]),onClick:n[1]||=e=>C.value=!C.value},q(U(u)?`⌘ Cmd`:`Ctrl`),3),U(u)?(G(),j(`button`,{key:0,class:V([`px-3 py-1.5 rounded-md text-xs font-mono border transition-colors`,w.value?`bg-primary/15 border-primary/30 text-primary`:`border-base op50 hover:op80`]),onClick:n[2]||=e=>w.value=!w.value},` \\u2303 Ctrl `,2)):A(``,!0),P(`button`,{class:V([`px-3 py-1.5 rounded-md text-xs font-mono border transition-colors`,T.value?`bg-primary/15 border-primary/30 text-primary`:`border-base op50 hover:op80`]),onClick:n[3]||=e=>T.value=!T.value},q(U(u)?`⌥ Alt`:`Alt`),3),P(`button`,{class:V([`px-3 py-1.5 rounded-md text-xs font-mono border transition-colors`,E.value?`bg-primary/15 border-primary/30 text-primary`:`border-base op50 hover:op80`]),onClick:n[4]||=e=>E.value=!E.value},q(U(u)?`⇧ Shift`:`Shift`),3)]),ae.value.length>0?(G(),j(`div`,ks,[(G(!0),j(H,null,L(ae.value,(e,t)=>(G(),j(`div`,{key:t,class:`flex items-center gap-1.5 text-xs text-amber`},[n[8]||=P(`div`,{class:`i-ph-warning-duotone w-3.5 h-3.5 shrink-0`},null,-1),M(` `+q(e),1)]))),128))])):A(``,!0),P(`div`,As,[P(`button`,{class:`px-3 py-1.5 rounded text-xs op60 hover:op100 hover:bg-gray/10 transition-colors`,onClick:re},` Cancel `),P(`button`,{class:V([`px-3 py-1.5 rounded text-xs transition-colors`,se.value?`bg-primary/15 text-primary hover:bg-primary/25`:`op30 cursor-not-allowed`]),disabled:!se.value,onClick:le},` OK `,10,js)])])],512)):A(``,!0)],64))}}),Ns={class:`h-full w-full overflow-hidden flex flex-col`},Ps={class:`flex items-center border-b border-base px-6 shrink-0`},Fs=[`onClick`],Is={key:0,class:`absolute bottom-0 left-2 right-2 h-0.5 bg-primary rounded-full`},Ls={class:`flex-1 overflow-auto p8`,style:{"scrollbar-gutter":`stable`}},Rs={class:`max-w-200 mx-auto`},zs=J({__name:`ViewBuiltinSettings`,props:{context:{},entry:{}},setup(e){let t=[{id:`appearance`,label:`Appearance`,icon:`i-ph-paint-brush-duotone`},{id:`docks`,label:`Docks`,icon:`i-ph-layout-duotone`},{id:`shortcuts`,label:`Shortcuts`,icon:`i-ph-keyboard-duotone`},{id:`advanced`,label:`Advanced`,icon:`i-ph-wrench-duotone`}],n=B(`appearance`);return(r,i)=>(G(),j(`div`,Ns,[P(`div`,Ps,[(G(),j(H,null,L(t,e=>P(`button`,{key:e.id,class:V([`flex items-center gap-1.5 px-4 py-3 text-sm transition-colors relative`,n.value===e.id?`op100 text-primary`:`op50 hover:op80`]),onClick:t=>n.value=e.id},[P(`div`,{class:V([e.icon,`w-4 h-4`])},null,2),M(` `+q(e.label)+` `,1),n.value===e.id?(G(),j(`div`,Is)):A(``,!0)],10,Fs)),64))]),P(`div`,Ls,[P(`div`,Rs,[n.value===`appearance`?(G(),F(Lo,{key:0,context:e.context},null,8,[`context`])):A(``,!0),n.value===`shortcuts`?(G(),F(Ms,{key:1,context:e.context},null,8,[`context`])):A(``,!0),n.value===`docks`?(G(),F(ds,{key:2,context:e.context},null,8,[`context`])):A(``,!0),n.value===`advanced`?(G(),F(bo,{key:3,context:e.context},null,8,[`context`])):A(``,!0)])])]))}}),Bs=J({__name:`ViewCustomRenderer`,props:{context:{},entry:{},panes:{},divStyle:{}},setup(e){let t=e,n=K(`viewFrame`);return Me(()=>{let e=t.panes.ensure(t.entry.id,{tagName:`div`}),r=e.element;r.style.boxShadow=`none`,r.style.outline=`none`,Object.assign(r.style,t.divStyle);let i=t.context.docks.getStateById(t.entry.id);i&&(i.domElements.panel=r),e.mount(n.value)}),xe(()=>{t.panes.get(t.entry.id)?.unmount()}),(e,t)=>(G(),j(`div`,{ref_key:`viewFrame`,ref:n,class:`devframes-view-custom-renderer w-full h-full flex items-center justify-center`},null,512))}}),Vs={class:`devframes-view-dock-renderer relative h-full w-full`},Hs={key:0,class:`absolute inset-0 flex flex-col items-center justify-center gap-2 p6 text-center`},Us={class:`text-sm color-muted`},Ws={key:1,class:`absolute inset-0 flex flex-col items-center justify-center gap-2 p6 text-center`},Gs={class:`text-sm color-muted`},Ks={key:0,class:`max-w-100 text-xs color-faint`},qs=J({__name:`ViewDockRenderer`,props:{context:{},entry:{}},setup(e){let t=e,n=K(`container`),r=B(`loading`),i=B(null),a,o=0;async function s(){let e=++o;if(a?.(),a=void 0,!t.context.renderers.has(t.entry.type)){r.value=`missing-renderer`;return}r.value=`loading`,i.value=null;let s=await t.context.renderers.mount(t.entry,n.value);if(e!==o){s.status===`mounted`&&s.dispose();return}s.status===`mounted`?(a=s.dispose,r.value=`mounted`):s.status===`missing-renderer`?r.value=`missing-renderer`:(r.value=`load-error`,i.value=String(s.error?.message??s.error))}return Me(()=>{s()}),Xe(()=>t.entry.id,()=>{s()}),xe(()=>{o++,a?.(),a=void 0}),(t,a)=>(G(),j(`div`,Vs,[Ze(P(`div`,{ref_key:`container`,ref:n,class:V([`h-full w-full`,U(Be)?`dark`:`light`])},null,2),[[k,r.value===`mounted`||r.value===`loading`]]),r.value===`missing-renderer`?(G(),j(`div`,Hs,[a[1]||=P(`div`,{class:`i-ph:puzzle-piece-duotone text-3xl color-faint`},null,-1),P(`div`,Us,` No renderer for “`+q(e.entry.type)+`” in the current environment `,1),a[2]||=P(`div`,{class:`text-xs color-faint`},` The host has not registered a renderer for this dock type. `,-1)])):r.value===`load-error`?(G(),j(`div`,Ws,[a[4]||=P(`div`,{class:`i-ph:warning-duotone text-3xl color-faint`},null,-1),P(`div`,Gs,` The renderer for “`+q(e.entry.type)+`” failed to load `,1),i.value?(G(),j(`div`,Ks,q(i.value),1)):A(``,!0),I(ma,{class:`mt-2`,onClick:a[0]||=e=>s()},{default:z(()=>[...a[3]||=[M(` Retry `,-1)]]),_:1})])):A(``,!0)]))}}),Js=[`top`,`right`,`bottom`,`left`],Ys=[`start`,`end`],Xs=Js.reduce((e,t)=>e.concat(t,t+`-`+Ys[0],t+`-`+Ys[1]),[]),Zs=Math.min,Qs=Math.max,$s={left:`right`,right:`left`,bottom:`top`,top:`bottom`};function ec(e,t,n){return Qs(e,Zs(t,n))}function tc(e,t){return typeof e==`function`?e(t):e}function nc(e){return e.split(`-`)[0]}function rc(e){return e.split(`-`)[1]}function ic(e){return e===`x`?`y`:`x`}function ac(e){return e===`y`?`height`:`width`}function oc(e){let t=e[0];return t===`t`||t===`b`?`y`:`x`}function sc(e){return ic(oc(e))}function cc(e,t,n){n===void 0&&(n=!1);let r=rc(e),i=sc(e),a=ac(i),o=i===`x`?r===(n?`end`:`start`)?`right`:`left`:r===`start`?`bottom`:`top`;return t.reference[a]>t.floating[a]&&(o=_c(o)),[o,_c(o)]}function lc(e){let t=_c(e);return[uc(e),t,uc(t)]}function uc(e){return e.includes(`start`)?e.replace(`start`,`end`):e.replace(`end`,`start`)}var dc=[`left`,`right`],fc=[`right`,`left`],pc=[`top`,`bottom`],mc=[`bottom`,`top`];function hc(e,t,n){switch(e){case`top`:case`bottom`:return n?t?fc:dc:t?dc:fc;case`left`:case`right`:return t?pc:mc;default:return[]}}function gc(e,t,n,r){let i=rc(e),a=hc(nc(e),n===`start`,r);return i&&(a=a.map(e=>e+`-`+i),t&&(a=a.concat(a.map(uc)))),a}function _c(e){let t=nc(e);return $s[t]+e.slice(t.length)}function vc(e){return{top:e.top??0,right:e.right??0,bottom:e.bottom??0,left:e.left??0}}function yc(e){return typeof e==`number`?{top:e,right:e,bottom:e,left:e}:vc(e)}function bc(e){let{x:t,y:n,width:r,height:i}=e;return{width:r,height:i,top:n,left:t,right:t+r,bottom:n+i,x:t,y:n}}function xc(e,t,n){let{reference:r,floating:i}=e,a=oc(t),o=sc(t),s=ac(o),c=nc(t),l=a===`y`,u=r.x+r.width/2-i.width/2,d=r.y+r.height/2-i.height/2,f=r[s]/2-i[s]/2,p;switch(c){case`top`:p={x:u,y:r.y-i.height};break;case`bottom`:p={x:u,y:r.y+r.height};break;case`right`:p={x:r.x+r.width,y:d};break;case`left`:p={x:r.x-i.width,y:d};break;default:p={x:r.x,y:r.y}}let m=rc(t);return m&&(p[o]+=f*(m===`end`?1:-1)*(n&&l?-1:1)),p}async function Sc(e,t){t===void 0&&(t={});let{x:n,y:r,platform:i,rects:a,elements:o,strategy:s}=e,{boundary:c=`clippingAncestors`,rootBoundary:l=`viewport`,elementContext:u=`floating`,altBoundary:d=!1,padding:f=0}=tc(t,e),p=yc(f),m=o[d?u===`floating`?`reference`:`floating`:u],h=bc(await i.getClippingRect({element:await(i.isElement==null?void 0:i.isElement(m))??!0?m:m.contextElement||await(i.getDocumentElement==null?void 0:i.getDocumentElement(o.floating)),boundary:c,rootBoundary:l,strategy:s})),g=u===`floating`?{x:n,y:r,width:a.floating.width,height:a.floating.height}:a.reference,_=await(i.getOffsetParent==null?void 0:i.getOffsetParent(o.floating)),v=await(i.isElement==null?void 0:i.isElement(_))&&await(i.getScale==null?void 0:i.getScale(_))||{x:1,y:1},y=bc(i.convertOffsetParentRelativeRectToViewportRelativeRect?await i.convertOffsetParentRelativeRectToViewportRelativeRect({elements:o,rect:g,offsetParent:_,strategy:s}):g);return{top:(h.top-y.top+p.top)/v.y,bottom:(y.bottom-h.bottom+p.bottom)/v.y,left:(h.left-y.left+p.left)/v.x,right:(y.right-h.right+p.right)/v.x}}var Cc=50,wc=async(e,t,n)=>{let{placement:r=`bottom`,strategy:i=`absolute`,middleware:a=[],platform:o}=n,s=o.detectOverflow?o:{...o,detectOverflow:Sc},c=await(o.isRTL==null?void 0:o.isRTL(t)),l=await o.getElementRects({reference:e,floating:t,strategy:i}),{x:u,y:d}=xc(l,r,c),f=r,p=0,m={};for(let n=0;n({name:`arrow`,options:e,async fn(t){let{x:n,y:r,placement:i,rects:a,platform:o,elements:s,middlewareData:c}=t,{element:l,padding:u=0}=tc(e,t)||{};if(l==null)return{};let d=yc(u),f={x:n,y:r},p=sc(i),m=ac(p),h=await o.getDimensions(l),g=p===`y`,_=g?`top`:`left`,v=g?`bottom`:`right`,y=g?`clientHeight`:`clientWidth`,b=a.reference[m]+a.reference[p]-f[p]-a.floating[m],x=f[p]-a.reference[p],S=await(o.getOffsetParent==null?void 0:o.getOffsetParent(l)),C=S?S[y]:0;(!C||!await(o.isElement==null?void 0:o.isElement(S)))&&(C=s.floating[y]||a.floating[m]);let w=b/2-x/2,T=C/2-h[m]/2-1,E=Zs(d[_],T),ee=Zs(d[v],T),te=C-h[m]-ee,D=C/2-h[m]/2+w,ne=ec(E,D,te),re=!c.arrow&&rc(i)!=null&&D!==ne&&a.reference[m]/2-(Drc(t)===e),...n.filter(t=>rc(t)!==e)]:n.filter(e=>nc(e)===e)).filter(n=>!e||rc(n)===e||(t?uc(n)!==n:!1))}var Dc=function(e){return e===void 0&&(e={}),{name:`autoPlacement`,options:e,async fn(t){let{rects:n,middlewareData:r,placement:i,platform:a,elements:o}=t,{crossAxis:s=!1,alignment:c,allowedPlacements:l=Xs,autoAlignment:u=!0,...d}=tc(e,t),f=c!==void 0||l===Xs?Ec(c||null,u,l):l,p=r.autoPlacement?.index||0,m=f[p];if(m==null)return{};if(i!==m)return{reset:{placement:f[0]}};let h=await a.detectOverflow(t,d),g=cc(m,n,await(a.isRTL==null?void 0:a.isRTL(o.floating))),_=[h[nc(m)],h[g[0]],h[g[1]]],v=[...r.autoPlacement?.overflows||[],{placement:m,overflows:_}],y=f[p+1];if(y)return{data:{index:p+1,overflows:v},reset:{placement:y}};let b=v.map(e=>{let t=rc(e.placement);return[e.placement,t&&s?e.overflows.slice(0,2).reduce((e,t)=>e+t,0):e.overflows[0],e.overflows]}).sort((e,t)=>e[1]-t[1]),x=b.filter(e=>e[2].slice(0,rc(e[0])?2:3).every(e=>e<=0))[0]?.[0]||b[0][0];return x===i?{}:{data:{index:p+1,overflows:v},reset:{placement:x}}}}},Oc=function(e){return e===void 0&&(e={}),{name:`flip`,options:e,async fn(t){var n;let{placement:r,middlewareData:i,rects:a,initialPlacement:o,platform:s,elements:c}=t,{mainAxis:l=!0,crossAxis:u=!0,fallbackPlacements:d,fallbackStrategy:f=`bestFit`,fallbackAxisSideDirection:p=`none`,flipAlignment:m=!0,...h}=tc(e,t);if((n=i.arrow)!=null&&n.alignmentOffset)return{};let g=nc(r),_=oc(o),v=nc(o)===o,y=await(s.isRTL==null?void 0:s.isRTL(c.floating)),b=d||(v||!m?[_c(o)]:lc(o)),x=p!==`none`;!d&&x&&b.push(...gc(o,m,p,y));let S=[o,...b],C=await s.detectOverflow(t,h),w=[],T=i.flip?.overflows||[];if(l&&w.push(C[g]),u){let e=cc(r,a,y);w.push(C[e[0]],C[e[1]])}if(T=[...T,{placement:r,overflows:w}],!w.every(e=>e<=0)){let e=(i.flip?.index||0)+1,t=S[e];if(t&&(u!==`alignment`||_===oc(t)||T.every(e=>oc(e.placement)!==_||e.overflows[0]>0)))return{data:{index:e,overflows:T},reset:{placement:t}};let n=T.filter(e=>e.overflows[0]<=0).sort((e,t)=>e.overflows[1]-t.overflows[1])[0]?.placement;if(!n)switch(f){case`bestFit`:{let e=T.filter(e=>{if(x){let t=oc(e.placement);return t===_||t===`y`}return!0}).map(e=>[e.placement,e.overflows.filter(e=>e>0).reduce((e,t)=>e+t,0)]).sort((e,t)=>e[1]-t[1])[0]?.[0];e&&(n=e);break}case`initialPlacement`:n=o}if(r!==n)return{reset:{placement:n}}}return{}}}},kc=new Set([`left`,`top`]);async function Ac(e,t){let{placement:n,platform:r,elements:i}=e,a=await(r.isRTL==null?void 0:r.isRTL(i.floating)),o=nc(n),s=rc(n),c=oc(n)===`y`,l=kc.has(o)?-1:1,u=a&&c?-1:1,d=tc(t,e),{mainAxis:f,crossAxis:p,alignmentAxis:m}=typeof d==`number`?{mainAxis:d,crossAxis:0,alignmentAxis:null}:{mainAxis:d.mainAxis||0,crossAxis:d.crossAxis||0,alignmentAxis:d.alignmentAxis};return s&&typeof m==`number`&&(p=s===`end`?m*-1:m),c?{x:p*u,y:f*l}:{x:f*l,y:p*u}}var jc=function(e){return e===void 0&&(e=0),{name:`offset`,options:e,async fn(t){var n;let{x:r,y:i,placement:a,middlewareData:o}=t,s=await Ac(t,e);return a===o.offset?.placement&&(n=o.arrow)!=null&&n.alignmentOffset?{}:{x:r+s.x,y:i+s.y,data:{...s,placement:a}}}}},Mc=function(e){return e===void 0&&(e={}),{name:`shift`,options:e,async fn(t){let{x:n,y:r,placement:i,platform:a}=t,{mainAxis:o=!0,crossAxis:s=!1,limiter:c={fn:e=>{let{x:t,y:n}=e;return{x:t,y:n}}},...l}=tc(e,t),u={x:n,y:r},d=await a.detectOverflow(t,l),f=oc(i),p=ic(f),m=u[p],h=u[f],g=(e,t)=>ec(t+d[e===`y`?`top`:`left`],t,t-d[e===`y`?`bottom`:`right`]);o&&(m=g(p,m)),s&&(h=g(f,h));let _=c.fn({...t,[p]:m,[f]:h});return{..._,data:{x:_.x-n,y:_.y-r,enabled:{[p]:o,[f]:s}}}}}},Nc=function(e){return e===void 0&&(e={}),{name:`size`,options:e,async fn(t){let{placement:n,rects:r,platform:i,elements:a}=t,{apply:o=()=>{},...s}=tc(e,t),c=await i.detectOverflow(t,s),l=nc(n),u=rc(n),d=oc(n)===`y`,{width:f,height:p}=r.floating,m,h;l===`top`||l===`bottom`?(m=l,h=u===(await(i.isRTL==null?void 0:i.isRTL(a.floating))?`start`:`end`)?`left`:`right`):(h=l,m=u===`end`?`top`:`bottom`);let g=p-c.top-c.bottom,_=f-c.left-c.right,v=Zs(p-c[m],g),y=Zs(f-c[h],_),b=t.middlewareData.shift,x=!b,S=v,C=y;b!=null&&b.enabled.x&&(C=_),b!=null&&b.enabled.y&&(S=g),x&&!u&&(d?C=f-2*Qs(c.left,c.right):S=p-2*Qs(c.top,c.bottom)),await o({...t,availableWidth:C,availableHeight:S});let w=await i.getDimensions(a.floating);return f!==w.width||p!==w.height?{reset:{rects:!0}}:{}}}};function Pc(e){return e.ownerDocument?.defaultView||window}function Fc(e){return Pc(e).getComputedStyle(e)}var Ic=Math.min,Lc=Math.max,Rc=Math.round;function zc(e){let t=Fc(e),n=parseFloat(t.width),r=parseFloat(t.height),i=e.offsetWidth,a=e.offsetHeight,o=Rc(n)!==i||Rc(r)!==a;return o&&(n=i,r=a),{width:n,height:r,fallback:o}}function Bc(e){return Gc(e)?(e.nodeName||``).toLowerCase():``}var Vc;function Hc(){if(Vc)return Vc;let e=navigator.userAgentData;return e&&Array.isArray(e.brands)?(Vc=e.brands.map((e=>e.brand+`/`+e.version)).join(` `),Vc):navigator.userAgent}function Uc(e){return e instanceof Pc(e).HTMLElement}function Wc(e){return e instanceof Pc(e).Element}function Gc(e){return e instanceof Pc(e).Node}function Kc(e){return typeof ShadowRoot>`u`?!1:e instanceof Pc(e).ShadowRoot||e instanceof ShadowRoot}function qc(e){let{overflow:t,overflowX:n,overflowY:r,display:i}=Fc(e);return/auto|scroll|overlay|hidden|clip/.test(t+r+n)&&![`inline`,`contents`].includes(i)}function Jc(e){return[`table`,`td`,`th`].includes(Bc(e))}function Yc(e){let t=/firefox/i.test(Hc()),n=Fc(e),r=n.backdropFilter||n.WebkitBackdropFilter;return n.transform!==`none`||n.perspective!==`none`||!!r&&r!==`none`||t&&n.willChange===`filter`||t&&!!n.filter&&n.filter!==`none`||[`transform`,`perspective`].some((e=>n.willChange.includes(e)))||[`paint`,`layout`,`strict`,`content`].some((e=>{let t=n.contain;return t!=null&&t.includes(e)}))}function Xc(){return!/^((?!chrome|android).)*safari/i.test(Hc())}function Zc(e){return[`html`,`body`,`#document`].includes(Bc(e))}function Qc(e){return Wc(e)?e:e.contextElement}var $c={x:1,y:1};function el(e){let t=Qc(e);if(!Uc(t))return $c;let n=t.getBoundingClientRect(),{width:r,height:i,fallback:a}=zc(t),o=(a?Rc(n.width):n.width)/r,s=(a?Rc(n.height):n.height)/i;return o&&Number.isFinite(o)||(o=1),s&&Number.isFinite(s)||(s=1),{x:o,y:s}}function tl(e,t,n,r){t===void 0&&(t=!1),n===void 0&&(n=!1);let i=e.getBoundingClientRect(),a=Qc(e),o=$c;t&&(r?Wc(r)&&(o=el(r)):o=el(e));let s=a?Pc(a):window,c=!Xc()&&n,l=(i.left+(c&&s.visualViewport?.offsetLeft||0))/o.x,u=(i.top+(c&&s.visualViewport?.offsetTop||0))/o.y,d=i.width/o.x,f=i.height/o.y;if(a){let e=Pc(a),t=r&&Wc(r)?Pc(r):r,n=e.frameElement;for(;n&&r&&t!==e;){let e=el(n),t=n.getBoundingClientRect(),r=getComputedStyle(n);t.x+=(n.clientLeft+parseFloat(r.paddingLeft))*e.x,t.y+=(n.clientTop+parseFloat(r.paddingTop))*e.y,l*=e.x,u*=e.y,d*=e.x,f*=e.y,l+=t.x,u+=t.y,n=Pc(n).frameElement}}return{width:d,height:f,top:u,right:l+d,bottom:u+f,left:l,x:l,y:u}}function nl(e){return((Gc(e)?e.ownerDocument:e.document)||window.document).documentElement}function rl(e){return Wc(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function il(e){return tl(nl(e)).left+rl(e).scrollLeft}function al(e){if(Bc(e)===`html`)return e;let t=e.assignedSlot||e.parentNode||Kc(e)&&e.host||nl(e);return Kc(t)?t.host:t}function ol(e){let t=al(e);return Zc(t)?t.ownerDocument.body:Uc(t)&&qc(t)?t:ol(t)}function sl(e,t){t===void 0&&(t=[]);let n=ol(e),r=n===e.ownerDocument?.body,i=Pc(n);return r?t.concat(i,i.visualViewport||[],qc(n)?n:[]):t.concat(n,sl(n))}function cl(e,t,n){return t===`viewport`?bc(function(e,t){let n=Pc(e),r=nl(e),i=n.visualViewport,a=r.clientWidth,o=r.clientHeight,s=0,c=0;if(i){a=i.width,o=i.height;let e=Xc();(e||!e&&t===`fixed`)&&(s=i.offsetLeft,c=i.offsetTop)}return{width:a,height:o,x:s,y:c}}(e,n)):Wc(t)?bc(function(e,t){let n=tl(e,!0,t===`fixed`),r=n.top+e.clientTop,i=n.left+e.clientLeft,a=Uc(e)?el(e):{x:1,y:1};return{width:e.clientWidth*a.x,height:e.clientHeight*a.y,x:i*a.x,y:r*a.y}}(t,n)):bc(function(e){let t=nl(e),n=rl(e),r=e.ownerDocument.body,i=Lc(t.scrollWidth,t.clientWidth,r.scrollWidth,r.clientWidth),a=Lc(t.scrollHeight,t.clientHeight,r.scrollHeight,r.clientHeight),o=-n.scrollLeft+il(e),s=-n.scrollTop;return Fc(r).direction===`rtl`&&(o+=Lc(t.clientWidth,r.clientWidth)-i),{width:i,height:a,x:o,y:s}}(nl(e)))}function ll(e){return Uc(e)&&Fc(e).position!==`fixed`?e.offsetParent:null}function ul(e){let t=Pc(e),n=ll(e);for(;n&&Jc(n)&&Fc(n).position===`static`;)n=ll(n);return n&&(Bc(n)===`html`||Bc(n)===`body`&&Fc(n).position===`static`&&!Yc(n))?t:n||function(e){let t=al(e);for(;Uc(t)&&!Zc(t);){if(Yc(t))return t;t=al(t)}return null}(e)||t}function dl(e,t,n){let r=Uc(t),i=nl(t),a=tl(e,!0,n===`fixed`,t),o={scrollLeft:0,scrollTop:0},s={x:0,y:0};if(r||!r&&n!==`fixed`){if((Bc(t)!==`body`||qc(i))&&(o=rl(t)),Uc(t)){let e=tl(t,!0);s.x=e.x+t.clientLeft,s.y=e.y+t.clientTop}else i&&(s.x=il(i))}return{x:a.left+o.scrollLeft-s.x,y:a.top+o.scrollTop-s.y,width:a.width,height:a.height}}var fl={getClippingRect:function(e){let{element:t,boundary:n,rootBoundary:r,strategy:i}=e,a=[...n===`clippingAncestors`?function(e,t){let n=t.get(e);if(n)return n;let r=sl(e).filter((e=>Wc(e)&&Bc(e)!==`body`)),i=null,a=Fc(e).position===`fixed`,o=a?al(e):e;for(;Wc(o)&&!Zc(o);){let e=Fc(o),t=Yc(o);(a?t||i:t||e.position!==`static`||!i||![`absolute`,`fixed`].includes(i.position))?i=e:r=r.filter((e=>e!==o)),o=al(o)}return t.set(e,r),r}(t,this._c):[].concat(n),r],o=a[0],s=a.reduce(((e,n)=>{let r=cl(t,n,i);return e.top=Lc(r.top,e.top),e.right=Ic(r.right,e.right),e.bottom=Ic(r.bottom,e.bottom),e.left=Lc(r.left,e.left),e}),cl(t,o,i));return{width:s.right-s.left,height:s.bottom-s.top,x:s.left,y:s.top}},convertOffsetParentRelativeRectToViewportRelativeRect:function(e){let{rect:t,offsetParent:n,strategy:r}=e,i=Uc(n),a=nl(n);if(n===a)return t;let o={scrollLeft:0,scrollTop:0},s={x:1,y:1},c={x:0,y:0};if((i||!i&&r!==`fixed`)&&((Bc(n)!==`body`||qc(a))&&(o=rl(n)),Uc(n))){let e=tl(n);s=el(n),c.x=e.x+n.clientLeft,c.y=e.y+n.clientTop}return{width:t.width*s.x,height:t.height*s.y,x:t.x*s.x-o.scrollLeft*s.x+c.x,y:t.y*s.y-o.scrollTop*s.y+c.y}},isElement:Wc,getDimensions:function(e){return Uc(e)?zc(e):e.getBoundingClientRect()},getOffsetParent:ul,getDocumentElement:nl,getScale:el,async getElementRects(e){let{reference:t,floating:n,strategy:r}=e,i=this.getOffsetParent||ul,a=this.getDimensions;return{reference:dl(t,await i(n),r),floating:{x:0,y:0,...await a(n)}}},getClientRects:e=>Array.from(e.getClientRects()),isRTL:e=>Fc(e).direction===`rtl`},pl=(e,t,n)=>{let r=new Map,i={platform:fl,...n},a={...i.platform,_c:r};return wc(e,t,{...i,platform:a})},ml={disabled:!1,distance:5,skidding:0,container:`body`,boundary:void 0,instantMove:!1,disposeTimeout:150,popperTriggers:[],strategy:`absolute`,preventOverflow:!0,flip:!0,shift:!0,overflowPadding:0,arrowPadding:0,arrowOverflow:!0,autoHideOnMousedown:!1,themes:{tooltip:{placement:`top`,triggers:[`hover`,`focus`,`touch`],hideTriggers:e=>[...e,`click`],delay:{show:200,hide:0},handleResize:!1,html:!1,loadingContent:`...`},dropdown:{placement:`bottom`,triggers:[`click`],delay:0,handleResize:!0,autoHide:!0},menu:{$extend:`dropdown`,triggers:[`hover`,`focus`],popperTriggers:[`hover`],delay:{show:0,hide:400}}}};function hl(e,t){let n=ml.themes[e]||{},r;do r=n[t],typeof r>`u`?n.$extend?n=ml.themes[n.$extend]||{}:(n=null,r=ml[t]):n=null;while(n);return r}function gl(e){let t=[e],n=ml.themes[e]||{};do n.$extend&&!n.$resetCss?(t.push(n.$extend),n=ml.themes[n.$extend]||{}):n=null;while(n);return t.map(e=>`v-popper--theme-${e}`)}function _l(e){let t=[e],n=ml.themes[e]||{};do n.$extend?(t.push(n.$extend),n=ml.themes[n.$extend]||{}):n=null;while(n);return t}var vl=!1;if(typeof window<`u`){vl=!1;try{let e=Object.defineProperty({},"passive",{get(){vl=!0}});window.addEventListener(`test`,null,e)}catch{}}var yl=!1;typeof window<`u`&&typeof navigator<`u`&&(yl=/iPad|iPhone|iPod/.test(navigator.userAgent)&&!window.MSStream);var bl=[`auto`,`top`,`bottom`,`left`,`right`].reduce((e,t)=>e.concat([t,`${t}-start`,`${t}-end`]),[]),xl={hover:`mouseenter`,focus:`focus`,click:`click`,touch:`touchstart`,pointer:`pointerdown`},Sl={hover:`mouseleave`,focus:`blur`,click:`click`,touch:`touchend`,pointer:`pointerup`};function Cl(e,t){let n=e.indexOf(t);n!==-1&&e.splice(n,1)}function wl(){return new Promise(e=>requestAnimationFrame(()=>{requestAnimationFrame(e)}))}var Tl=[],El=null,Dl={};function Ol(e){let t=Dl[e];return t||=Dl[e]=[],t}var kl=function(){};typeof window<`u`&&(kl=window.Element);function $(e){return function(t){return hl(t.theme,e)}}var Al=`__floating-vue__popper`,jl=()=>J({name:`VPopper`,provide(){return{[Al]:{parentPopper:this}}},inject:{[Al]:{default:null}},props:{theme:{type:String,required:!0},targetNodes:{type:Function,required:!0},referenceNode:{type:Function,default:null},popperNode:{type:Function,required:!0},shown:{type:Boolean,default:!1},showGroup:{type:String,default:null},ariaId:{default:null},disabled:{type:Boolean,default:$(`disabled`)},positioningDisabled:{type:Boolean,default:$(`positioningDisabled`)},placement:{type:String,default:$(`placement`),validator:e=>bl.includes(e)},delay:{type:[String,Number,Object],default:$(`delay`)},distance:{type:[Number,String],default:$(`distance`)},skidding:{type:[Number,String],default:$(`skidding`)},triggers:{type:Array,default:$(`triggers`)},showTriggers:{type:[Array,Function],default:$(`showTriggers`)},hideTriggers:{type:[Array,Function],default:$(`hideTriggers`)},popperTriggers:{type:Array,default:$(`popperTriggers`)},popperShowTriggers:{type:[Array,Function],default:$(`popperShowTriggers`)},popperHideTriggers:{type:[Array,Function],default:$(`popperHideTriggers`)},container:{type:[String,Object,kl,Boolean],default:$(`container`)},boundary:{type:[String,kl],default:$(`boundary`)},strategy:{type:String,validator:e=>[`absolute`,`fixed`].includes(e),default:$(`strategy`)},autoHide:{type:[Boolean,Function],default:$(`autoHide`)},handleResize:{type:Boolean,default:$(`handleResize`)},instantMove:{type:Boolean,default:$(`instantMove`)},eagerMount:{type:Boolean,default:$(`eagerMount`)},popperClass:{type:[String,Array,Object],default:$(`popperClass`)},computeTransformOrigin:{type:Boolean,default:$(`computeTransformOrigin`)},autoMinSize:{type:Boolean,default:$(`autoMinSize`)},autoSize:{type:[Boolean,String],default:$(`autoSize`)},autoMaxSize:{type:Boolean,default:$(`autoMaxSize`)},autoBoundaryMaxSize:{type:Boolean,default:$(`autoBoundaryMaxSize`)},preventOverflow:{type:Boolean,default:$(`preventOverflow`)},overflowPadding:{type:[Number,String],default:$(`overflowPadding`)},arrowPadding:{type:[Number,String],default:$(`arrowPadding`)},arrowOverflow:{type:Boolean,default:$(`arrowOverflow`)},flip:{type:Boolean,default:$(`flip`)},shift:{type:Boolean,default:$(`shift`)},shiftCrossAxis:{type:Boolean,default:$(`shiftCrossAxis`)},noAutoFocus:{type:Boolean,default:$(`noAutoFocus`)},disposeTimeout:{type:Number,default:$(`disposeTimeout`)}},emits:{show:()=>!0,hide:()=>!0,"update:shown":e=>!0,"apply-show":()=>!0,"apply-hide":()=>!0,"close-group":()=>!0,"close-directive":()=>!0,"auto-hide":()=>!0,resize:()=>!0},data(){return{isShown:!1,isMounted:!1,skipTransition:!1,classes:{showFrom:!1,showTo:!1,hideFrom:!1,hideTo:!0},result:{x:0,y:0,placement:``,strategy:this.strategy,arrow:{x:0,y:0,centerOffset:0},transformOrigin:null},randomId:`popper_${[Math.random(),Date.now()].map(e=>e.toString(36).substring(2,10)).join(`_`)}`,shownChildren:new Set,lastAutoHide:!0,pendingHide:!1,containsGlobalTarget:!1,isDisposed:!0,mouseDownContains:!1}},computed:{popperId(){return this.ariaId==null?this.randomId:this.ariaId},shouldMountContent(){return this.eagerMount||this.isMounted},slotData(){return{popperId:this.popperId,isShown:this.isShown,shouldMountContent:this.shouldMountContent,skipTransition:this.skipTransition,autoHide:typeof this.autoHide==`function`?this.lastAutoHide:this.autoHide,show:this.show,hide:this.hide,handleResize:this.handleResize,onResize:this.onResize,classes:{...this.classes,popperClass:this.popperClass},result:this.positioningDisabled?null:this.result,attrs:this.$attrs}},parentPopper(){return this[Al]?.parentPopper},hasPopperShowTriggerHover(){return this.popperTriggers?.includes(`hover`)||this.popperShowTriggers?.includes(`hover`)}},watch:{shown:`$_autoShowHide`,disabled(e){e?this.dispose():this.init()},async container(){this.isShown&&(this.$_ensureTeleport(),await this.$_computePosition())},triggers:{handler:`$_refreshListeners`,deep:!0},positioningDisabled:`$_refreshListeners`,...[`placement`,`distance`,`skidding`,`boundary`,`strategy`,`overflowPadding`,`arrowPadding`,`preventOverflow`,`shift`,`shiftCrossAxis`,`flip`].reduce((e,t)=>(e[t]=`$_computePosition`,e),{})},created(){this.autoMinSize&&console.warn('[floating-vue] `autoMinSize` option is deprecated. Use `autoSize="min"` instead.'),this.autoMaxSize&&console.warn("[floating-vue] `autoMaxSize` option is deprecated. Use `autoBoundaryMaxSize` instead.")},mounted(){this.init(),this.$_detachPopperNode()},activated(){this.$_autoShowHide()},deactivated(){this.hide()},beforeUnmount(){this.dispose()},methods:{show({event:e=null,skipDelay:t=!1,force:n=!1}={}){var r;(r=this.parentPopper)!=null&&r.lockedChild&&this.parentPopper.lockedChild!==this||(this.pendingHide=!1,(n||!this.disabled)&&(this.parentPopper?.lockedChild===this&&(this.parentPopper.lockedChild=null),this.$_scheduleShow(e,t),this.$emit(`show`),this.$_showFrameLocked=!0,requestAnimationFrame(()=>{this.$_showFrameLocked=!1})),this.$emit(`update:shown`,!0))},hide({event:e=null,skipDelay:t=!1}={}){if(!this.$_hideInProgress){if(this.shownChildren.size>0){this.pendingHide=!0;return}if(this.hasPopperShowTriggerHover&&this.$_isAimingPopper()){this.parentPopper&&(this.parentPopper.lockedChild=this,clearTimeout(this.parentPopper.lockedChildTimer),this.parentPopper.lockedChildTimer=setTimeout(()=>{this.parentPopper.lockedChild===this&&(this.parentPopper.lockedChild.hide({skipDelay:t}),this.parentPopper.lockedChild=null)},1e3));return}this.parentPopper?.lockedChild===this&&(this.parentPopper.lockedChild=null),this.pendingHide=!1,this.$_scheduleHide(e,t),this.$emit(`hide`),this.$emit(`update:shown`,!1)}},init(){this.isDisposed&&(this.isDisposed=!1,this.isMounted=!1,this.$_events=[],this.$_preventShow=!1,this.$_referenceNode=this.referenceNode?.call(this)??this.$el,this.$_targetNodes=this.targetNodes().filter(e=>e.nodeType===e.ELEMENT_NODE),this.$_popperNode=this.popperNode(),this.$_innerNode=this.$_popperNode.querySelector(`.v-popper__inner`),this.$_arrowNode=this.$_popperNode.querySelector(`.v-popper__arrow-container`),this.$_swapTargetAttrs(`title`,`data-original-title`),this.$_detachPopperNode(),this.triggers.length&&this.$_addEventListeners(),this.shown&&this.show())},dispose(){this.isDisposed||(this.isDisposed=!0,this.$_removeEventListeners(),this.hide({skipDelay:!0}),this.$_detachPopperNode(),this.isMounted=!1,this.isShown=!1,this.$_updateParentShownChildren(!1),this.$_swapTargetAttrs(`data-original-title`,`title`))},async onResize(){this.isShown&&(await this.$_computePosition(),this.$emit(`resize`))},async $_computePosition(){if(this.isDisposed||this.positioningDisabled)return;let e={strategy:this.strategy,middleware:[]};(this.distance||this.skidding)&&e.middleware.push(jc({mainAxis:this.distance,crossAxis:this.skidding}));let t=this.placement.startsWith(`auto`);if(t?e.middleware.push(Dc({alignment:this.placement.split(`-`)[1]??``})):e.placement=this.placement,this.preventOverflow&&(this.shift&&e.middleware.push(Mc({padding:this.overflowPadding,boundary:this.boundary,crossAxis:this.shiftCrossAxis})),!t&&this.flip&&e.middleware.push(Oc({padding:this.overflowPadding,boundary:this.boundary}))),e.middleware.push(Tc({element:this.$_arrowNode,padding:this.arrowPadding})),this.arrowOverflow&&e.middleware.push({name:`arrowOverflow`,fn:({placement:e,rects:t,middlewareData:n})=>{let r,{centerOffset:i}=n.arrow;return r=e.startsWith(`top`)||e.startsWith(`bottom`)?Math.abs(i)>t.reference.width/2:Math.abs(i)>t.reference.height/2,{data:{overflow:r}}}}),this.autoMinSize||this.autoSize){let t=this.autoSize?this.autoSize:this.autoMinSize?`min`:null;e.middleware.push({name:`autoSize`,fn:({rects:e,placement:n,middlewareData:r})=>{var i;if((i=r.autoSize)!=null&&i.skip)return{};let a,o;return n.startsWith(`top`)||n.startsWith(`bottom`)?a=e.reference.width:o=e.reference.height,this.$_innerNode.style[t===`min`?`minWidth`:t===`max`?`maxWidth`:`width`]=a==null?null:`${a}px`,this.$_innerNode.style[t===`min`?`minHeight`:t===`max`?`maxHeight`:`height`]=o==null?null:`${o}px`,{data:{skip:!0},reset:{rects:!0}}}})}(this.autoMaxSize||this.autoBoundaryMaxSize)&&(this.$_innerNode.style.maxWidth=null,this.$_innerNode.style.maxHeight=null,e.middleware.push(Nc({boundary:this.boundary,padding:this.overflowPadding,apply:({availableWidth:e,availableHeight:t})=>{this.$_innerNode.style.maxWidth=e==null?null:`${e}px`,this.$_innerNode.style.maxHeight=t==null?null:`${t}px`}})));let n=await pl(this.$_referenceNode,this.$_popperNode,e);Object.assign(this.result,{x:n.x,y:n.y,placement:n.placement,strategy:n.strategy,arrow:{...n.middlewareData.arrow,...n.middlewareData.arrowOverflow}})},$_scheduleShow(e,t=!1){if(this.$_updateParentShownChildren(!0),this.$_hideInProgress=!1,clearTimeout(this.$_scheduleTimer),El&&this.instantMove&&El.instantMove&&El!==this.parentPopper){El.$_applyHide(!0),this.$_applyShow(!0);return}t?this.$_applyShow():this.$_scheduleTimer=setTimeout(this.$_applyShow.bind(this),this.$_computeDelay(`show`))},$_scheduleHide(e,t=!1){if(this.shownChildren.size>0){this.pendingHide=!0;return}this.$_updateParentShownChildren(!1),this.$_hideInProgress=!0,clearTimeout(this.$_scheduleTimer),this.isShown&&(El=this),t?this.$_applyHide():this.$_scheduleTimer=setTimeout(this.$_applyHide.bind(this),this.$_computeDelay(`hide`))},$_computeDelay(e){let t=this.delay;return parseInt(t&&t[e]||t||0)},async $_applyShow(e=!1){clearTimeout(this.$_disposeTimer),clearTimeout(this.$_scheduleTimer),this.skipTransition=e,!this.isShown&&(this.$_ensureTeleport(),await wl(),await this.$_computePosition(),await this.$_applyShowEffect(),this.positioningDisabled||this.$_registerEventListeners([...sl(this.$_referenceNode),...sl(this.$_popperNode)],`scroll`,()=>{this.$_computePosition()}))},async $_applyShowEffect(){if(this.$_hideInProgress)return;if(this.computeTransformOrigin){let e=this.$_referenceNode.getBoundingClientRect(),t=this.$_popperNode.querySelector(`.v-popper__wrapper`),n=t.parentNode.getBoundingClientRect(),r=e.x+e.width/2-(n.left+t.offsetLeft),i=e.y+e.height/2-(n.top+t.offsetTop);this.result.transformOrigin=`${r}px ${i}px`}this.isShown=!0,this.$_applyAttrsToTarget({"aria-describedby":this.popperId,"data-popper-shown":``});let e=this.showGroup;if(e){let t;for(let n=0;n0){this.pendingHide=!0,this.$_hideInProgress=!1;return}if(clearTimeout(this.$_scheduleTimer),!this.isShown)return;this.skipTransition=e,Cl(Tl,this),Tl.length===0&&document.body.classList.remove(`v-popper--some-open`);for(let e of _l(this.theme)){let t=Ol(e);Cl(t,this),t.length===0&&document.body.classList.remove(`v-popper--some-open--${e}`)}El===this&&(El=null),this.isShown=!1,this.$_applyAttrsToTarget({"aria-describedby":void 0,"data-popper-shown":void 0}),clearTimeout(this.$_disposeTimer);let t=this.disposeTimeout;t!==null&&(this.$_disposeTimer=setTimeout(()=>{this.$_popperNode&&(this.$_detachPopperNode(),this.isMounted=!1)},t)),this.$_removeEventListeners(`scroll`),this.$emit(`apply-hide`),this.classes.showFrom=!1,this.classes.showTo=!1,this.classes.hideFrom=!0,this.classes.hideTo=!1,await wl(),this.classes.hideFrom=!1,this.classes.hideTo=!0},$_autoShowHide(){this.shown?this.show():this.hide()},$_ensureTeleport(){if(this.isDisposed)return;let e=this.container;if(typeof e==`string`?e=window.document.querySelector(e):e===!1&&(e=this.$_targetNodes[0].parentNode),!e)throw Error(`No container for popover: `+this.container);e.appendChild(this.$_popperNode),this.isMounted=!0},$_addEventListeners(){let e=e=>{this.isShown&&!this.$_hideInProgress||(e.usedByTooltip=!0,!this.$_preventShow&&this.show({event:e}))};this.$_registerTriggerListeners(this.$_targetNodes,xl,this.triggers,this.showTriggers,e),this.$_registerTriggerListeners([this.$_popperNode],xl,this.popperTriggers,this.popperShowTriggers,e);let t=e=>{e.usedByTooltip||this.hide({event:e})};this.$_registerTriggerListeners(this.$_targetNodes,Sl,this.triggers,this.hideTriggers,t),this.$_registerTriggerListeners([this.$_popperNode],Sl,this.popperTriggers,this.popperHideTriggers,t)},$_registerEventListeners(e,t,n){this.$_events.push({targetNodes:e,eventType:t,handler:n}),e.forEach(e=>e.addEventListener(t,n,vl?{passive:!0}:void 0))},$_registerTriggerListeners(e,t,n,r,i){let a=n;r!=null&&(a=typeof r==`function`?r(a):r),a.forEach(n=>{let r=t[n];r&&this.$_registerEventListeners(e,r,i)})},$_removeEventListeners(e){let t=[];this.$_events.forEach(n=>{let{targetNodes:r,eventType:i,handler:a}=n;!e||e===i?r.forEach(e=>e.removeEventListener(i,a)):t.push(n)}),this.$_events=t},$_refreshListeners(){this.isDisposed||(this.$_removeEventListeners(),this.$_addEventListeners())},$_handleGlobalClose(e,t=!1){this.$_showFrameLocked||(this.hide({event:e}),e.closePopover?this.$emit(`close-directive`):this.$emit(`auto-hide`),t&&(this.$_preventShow=!0,setTimeout(()=>{this.$_preventShow=!1},300)))},$_detachPopperNode(){this.$_popperNode.parentNode&&this.$_popperNode.parentNode.removeChild(this.$_popperNode)},$_swapTargetAttrs(e,t){for(let n of this.$_targetNodes){let r=n.getAttribute(e);r&&(n.removeAttribute(e),n.setAttribute(t,r))}},$_applyAttrsToTarget(e){for(let t of this.$_targetNodes)for(let n in e){let r=e[n];r==null?t.removeAttribute(n):t.setAttribute(n,r)}},$_updateParentShownChildren(e){let t=this.parentPopper;for(;t;)e?t.shownChildren.add(this.randomId):(t.shownChildren.delete(this.randomId),t.pendingHide&&t.hide()),t=t.parentPopper},$_isAimingPopper(){let e=this.$_referenceNode.getBoundingClientRect();if(Bl>=e.left&&Bl<=e.right&&Vl>=e.top&&Vl<=e.bottom){let e=this.$_popperNode.getBoundingClientRect(),t=Bl-Rl,n=Vl-zl,r=e.left+e.width/2-Rl+(e.top+e.height/2)-zl+e.width+e.height,i=Rl+t*r,a=zl+n*r;return Hl(Rl,zl,i,a,e.left,e.top,e.left,e.bottom)||Hl(Rl,zl,i,a,e.left,e.top,e.right,e.top)||Hl(Rl,zl,i,a,e.right,e.top,e.right,e.bottom)||Hl(Rl,zl,i,a,e.left,e.bottom,e.right,e.bottom)}return!1}},render(){return this.$slots.default(this.slotData)}});if(typeof document<`u`&&typeof window<`u`){if(yl){let e=!vl||{passive:!0,capture:!0};document.addEventListener(`touchstart`,e=>Ml(e,!0),e),document.addEventListener(`touchend`,e=>Nl(e,!0),e)}else window.addEventListener(`mousedown`,e=>Ml(e,!1),!0),window.addEventListener(`click`,e=>Nl(e,!1),!0);window.addEventListener(`resize`,Ll)}function Ml(e,t){if(ml.autoHideOnMousedown)Pl(e,t);else for(let t=0;t=0;r--){let i=Tl[r];try{let r=i.containsGlobalTarget=i.mouseDownContains||i.popperNode().contains(e.target);i.pendingHide=!1,requestAnimationFrame(()=>{if(i.pendingHide=!1,!n[i.randomId]&&Fl(i,r,e)){if(i.$_handleGlobalClose(e,t),!e.closeAllPopover&&e.closePopover&&r){let e=i.parentPopper;for(;e;)n[e.randomId]=!0,e=e.parentPopper;return}let a=i.parentPopper;for(;a&&Fl(a,a.containsGlobalTarget,e);)a.$_handleGlobalClose(e,t),a=a.parentPopper}})}catch{}}}function Fl(e,t,n){return n.closeAllPopover||n.closePopover&&t||Il(e,n)&&!t}function Il(e,t){if(typeof e.autoHide==`function`){let n=e.autoHide(t);return e.lastAutoHide=n,n}return e.autoHide}function Ll(){for(let e=0;e{Rl=Bl,zl=Vl,Bl=e.clientX,Vl=e.clientY},vl?{passive:!0}:void 0);function Hl(e,t,n,r,i,a,o,s){let c=((o-i)*(t-a)-(s-a)*(e-i))/((s-a)*(n-e)-(o-i)*(r-t)),l=((n-e)*(t-a)-(r-t)*(e-i))/((s-a)*(n-e)-(o-i)*(r-t));return c>=0&&c<=1&&l>=0&&l<=1}var Ul={extends:jl()},Wl=(e,t)=>{let n=e.__vccOpts||e;for(let[e,r]of t)n[e]=r;return n};function Gl(e,t,n,r,i,a){return G(),j(`div`,{ref:`reference`,class:V([`v-popper`,{"v-popper--shown":e.slotData.isShown}])},[Pe(e.$slots,`default`,Ue(_e(e.slotData)))],2)}var Kl=Wl(Ul,[[`render`,Gl]]);function ql(){var e=window.navigator.userAgent,t=e.indexOf(`MSIE `);if(t>0)return parseInt(e.substring(t+5,e.indexOf(`.`,t)),10);if(e.indexOf(`Trident/`)>0){var n=e.indexOf(`rv:`);return parseInt(e.substring(n+3,e.indexOf(`.`,n)),10)}var r=e.indexOf(`Edge/`);return r>0?parseInt(e.substring(r+5,e.indexOf(`.`,r)),10):-1}var Jl;function Yl(){Yl.init||(Yl.init=!0,Jl=ql()!==-1)}var Xl={name:`ResizeObserver`,props:{emitOnMount:{type:Boolean,default:!1},ignoreWidth:{type:Boolean,default:!1},ignoreHeight:{type:Boolean,default:!1}},emits:[`notify`],mounted(){Yl(),Ae(()=>{this._w=this.$el.offsetWidth,this._h=this.$el.offsetHeight,this.emitOnMount&&this.emitSize()});let e=document.createElement(`object`);this._resizeObject=e,e.setAttribute(`aria-hidden`,`true`),e.setAttribute(`tabindex`,-1),e.onload=this.addResizeHandlers,e.type=`text/html`,Jl&&this.$el.appendChild(e),e.data=`about:blank`,Jl||this.$el.appendChild(e)},beforeUnmount(){this.removeResizeHandlers()},methods:{compareAndNotify(){(!this.ignoreWidth&&this._w!==this.$el.offsetWidth||!this.ignoreHeight&&this._h!==this.$el.offsetHeight)&&(this._w=this.$el.offsetWidth,this._h=this.$el.offsetHeight,this.emitSize())},emitSize(){this.$emit(`notify`,{width:this._w,height:this._h})},addResizeHandlers(){this._resizeObject.contentDocument.defaultView.addEventListener(`resize`,this.compareAndNotify),this.compareAndNotify()},removeResizeHandlers(){this._resizeObject&&this._resizeObject.onload&&(!Jl&&this._resizeObject.contentDocument&&this._resizeObject.contentDocument.defaultView.removeEventListener(`resize`,this.compareAndNotify),this.$el.removeChild(this._resizeObject),this._resizeObject.onload=null,this._resizeObject=null)}}},Zl=et(`data-v-b329ee4c`);Ne(`data-v-b329ee4c`);var Ql={class:`resize-observer`,tabindex:`-1`};Ce(),Xl.render=Zl((e,t,n,r,i,a)=>(G(),F(`div`,Ql))),Xl.__scopeId=`data-v-b329ee4c`,Xl.__file=`src/components/ResizeObserver.vue`;var $l=(e=`theme`)=>({computed:{themeClass(){return gl(this[e])}}}),eu=J({name:`VPopperContent`,components:{ResizeObserver:Xl},mixins:[$l()],props:{popperId:String,theme:String,shown:Boolean,mounted:Boolean,skipTransition:Boolean,autoHide:Boolean,handleResize:Boolean,classes:Object,result:Object},emits:[`hide`,`resize`],methods:{toPx(e){return e!=null&&!isNaN(e)?`${e}px`:null}}}),tu=[`id`,`aria-hidden`,`tabindex`,`data-popper-placement`],nu={ref:`inner`,class:`v-popper__inner`},ru=[P(`div`,{class:`v-popper__arrow-outer`},null,-1),P(`div`,{class:`v-popper__arrow-inner`},null,-1)];function iu(e,t,n,r,i,a){let o=De(`ResizeObserver`);return G(),j(`div`,{id:e.popperId,ref:`popover`,class:V([`v-popper__popper`,[e.themeClass,e.classes.popperClass,{"v-popper__popper--shown":e.shown,"v-popper__popper--hidden":!e.shown,"v-popper__popper--show-from":e.classes.showFrom,"v-popper__popper--show-to":e.classes.showTo,"v-popper__popper--hide-from":e.classes.hideFrom,"v-popper__popper--hide-to":e.classes.hideTo,"v-popper__popper--skip-transition":e.skipTransition,"v-popper__popper--arrow-overflow":e.result&&e.result.arrow.overflow,"v-popper__popper--no-positioning":!e.result}]]),style:R(e.result?{position:e.result.strategy,transform:`translate3d(${Math.round(e.result.x)}px,${Math.round(e.result.y)}px,0)`}:void 0),"aria-hidden":e.shown?`false`:`true`,tabindex:e.autoHide?0:void 0,"data-popper-placement":e.result?e.result.placement:void 0,onKeyup:t[2]||=ye(t=>e.autoHide&&e.$emit(`hide`),[`esc`])},[P(`div`,{class:`v-popper__backdrop`,onClick:t[0]||=t=>e.autoHide&&e.$emit(`hide`)}),P(`div`,{class:`v-popper__wrapper`,style:R(e.result?{transformOrigin:e.result.transformOrigin}:void 0)},[P(`div`,nu,[e.mounted?(G(),j(H,{key:0},[P(`div`,null,[Pe(e.$slots,`default`)]),e.handleResize?(G(),F(o,{key:0,onNotify:t[1]||=t=>e.$emit(`resize`,t)})):A(``,!0)],64)):A(``,!0)],512),P(`div`,{ref:`arrow`,class:`v-popper__arrow-container`,style:R(e.result?{left:e.toPx(e.result.arrow.x),top:e.toPx(e.result.arrow.y)}:void 0)},ru,4)],4)],46,tu)}var au=Wl(eu,[[`render`,iu]]),ou={methods:{show(...e){return this.$refs.popper.show(...e)},hide(...e){return this.$refs.popper.hide(...e)},dispose(...e){return this.$refs.popper.dispose(...e)},onResize(...e){return this.$refs.popper.onResize(...e)}}},su=function(){};typeof window<`u`&&(su=window.Element);var cu=J({name:`VPopperWrapper`,components:{Popper:Kl,PopperContent:au},mixins:[ou,$l(`finalTheme`)],props:{theme:{type:String,default:null},referenceNode:{type:Function,default:null},shown:{type:Boolean,default:!1},showGroup:{type:String,default:null},ariaId:{default:null},disabled:{type:Boolean,default:void 0},positioningDisabled:{type:Boolean,default:void 0},placement:{type:String,default:void 0},delay:{type:[String,Number,Object],default:void 0},distance:{type:[Number,String],default:void 0},skidding:{type:[Number,String],default:void 0},triggers:{type:Array,default:void 0},showTriggers:{type:[Array,Function],default:void 0},hideTriggers:{type:[Array,Function],default:void 0},popperTriggers:{type:Array,default:void 0},popperShowTriggers:{type:[Array,Function],default:void 0},popperHideTriggers:{type:[Array,Function],default:void 0},container:{type:[String,Object,su,Boolean],default:void 0},boundary:{type:[String,su],default:void 0},strategy:{type:String,default:void 0},autoHide:{type:[Boolean,Function],default:void 0},handleResize:{type:Boolean,default:void 0},instantMove:{type:Boolean,default:void 0},eagerMount:{type:Boolean,default:void 0},popperClass:{type:[String,Array,Object],default:void 0},computeTransformOrigin:{type:Boolean,default:void 0},autoMinSize:{type:Boolean,default:void 0},autoSize:{type:[Boolean,String],default:void 0},autoMaxSize:{type:Boolean,default:void 0},autoBoundaryMaxSize:{type:Boolean,default:void 0},preventOverflow:{type:Boolean,default:void 0},overflowPadding:{type:[Number,String],default:void 0},arrowPadding:{type:[Number,String],default:void 0},arrowOverflow:{type:Boolean,default:void 0},flip:{type:Boolean,default:void 0},shift:{type:Boolean,default:void 0},shiftCrossAxis:{type:Boolean,default:void 0},noAutoFocus:{type:Boolean,default:void 0},disposeTimeout:{type:Number,default:void 0}},emits:{show:()=>!0,hide:()=>!0,"update:shown":e=>!0,"apply-show":()=>!0,"apply-hide":()=>!0,"close-group":()=>!0,"close-directive":()=>!0,"auto-hide":()=>!0,resize:()=>!0},computed:{finalTheme(){return this.theme??this.$options.vPopperTheme}},methods:{getTargetNodes(){return Array.from(this.$el.children).filter(e=>e!==this.$refs.popperContent.$el)}}});function lu(e,t,n,r,i,a){let o=De(`PopperContent`),s=De(`Popper`);return G(),F(s,Se({ref:`popper`},e.$props,{theme:e.finalTheme,"target-nodes":e.getTargetNodes,"popper-node":()=>e.$refs.popperContent.$el,class:[e.themeClass],onShow:t[0]||=()=>e.$emit(`show`),onHide:t[1]||=()=>e.$emit(`hide`),"onUpdate:shown":t[2]||=t=>e.$emit(`update:shown`,t),onApplyShow:t[3]||=()=>e.$emit(`apply-show`),onApplyHide:t[4]||=()=>e.$emit(`apply-hide`),onCloseGroup:t[5]||=()=>e.$emit(`close-group`),onCloseDirective:t[6]||=()=>e.$emit(`close-directive`),onAutoHide:t[7]||=()=>e.$emit(`auto-hide`),onResize:t[8]||=()=>e.$emit(`resize`)}),{default:z(({popperId:t,isShown:n,shouldMountContent:r,skipTransition:i,autoHide:a,show:s,hide:c,handleResize:l,onResize:u,classes:d,result:f})=>[Pe(e.$slots,`default`,{shown:n,show:s,hide:c}),I(o,{ref:`popperContent`,"popper-id":t,theme:e.finalTheme,shown:n,mounted:r,"skip-transition":i,"auto-hide":a,"handle-resize":l,classes:d,result:f,onHide:c,onResize:u},{default:z(()=>[Pe(e.$slots,`popper`,{shown:n,hide:c})]),_:2},1032,[`popper-id`,`theme`,`shown`,`mounted`,`skip-transition`,`auto-hide`,`handle-resize`,`classes`,`result`,`onHide`,`onResize`])]),_:3},16,[`theme`,`target-nodes`,`popper-node`,`class`])}var uu=Wl(cu,[[`render`,lu]]);({...uu}),{...uu},{...uu};var du=J({name:`VTooltipDirective`,components:{Popper:jl(),PopperContent:au},mixins:[ou],inheritAttrs:!1,props:{theme:{type:String,default:`tooltip`},html:{type:Boolean,default:e=>hl(e.theme,`html`)},content:{type:[String,Number,Function],default:null},loadingContent:{type:String,default:e=>hl(e.theme,`loadingContent`)},targetNodes:{type:Function,required:!0}},data(){return{asyncContent:null}},computed:{isContentAsync(){return typeof this.content==`function`},loading(){return this.isContentAsync&&this.asyncContent==null},finalContent(){return this.isContentAsync?this.loading?this.loadingContent:this.asyncContent:this.content}},watch:{content:{handler(){this.fetchContent(!0)},immediate:!0},async finalContent(){await this.$nextTick(),this.$refs.popper.onResize()}},created(){this.$_fetchId=0},methods:{fetchContent(e){if(typeof this.content==`function`&&this.$_isShown&&(e||!this.$_loading&&this.asyncContent==null)){this.asyncContent=null,this.$_loading=!0;let e=++this.$_fetchId,t=this.content(this);t.then?t.then(t=>this.onResult(e,t)):this.onResult(e,t)}},onResult(e,t){e===this.$_fetchId&&(this.$_loading=!1,this.asyncContent=t)},onShow(){this.$_isShown=!0,this.fetchContent()},onHide(){this.$_isShown=!1}}}),fu=[`innerHTML`],pu=[`textContent`];function mu(e,t,n,r,i,a){let o=De(`PopperContent`),s=De(`Popper`);return G(),F(s,Se({ref:`popper`},e.$attrs,{theme:e.theme,"target-nodes":e.targetNodes,"popper-node":()=>e.$refs.popperContent.$el,onApplyShow:e.onShow,onApplyHide:e.onHide}),{default:z(({popperId:t,isShown:n,shouldMountContent:r,skipTransition:i,autoHide:a,hide:s,handleResize:c,onResize:l,classes:u,result:d})=>[I(o,{ref:`popperContent`,class:V({"v-popper--tooltip-loading":e.loading}),"popper-id":t,theme:e.theme,shown:n,mounted:r,"skip-transition":i,"auto-hide":a,"handle-resize":c,classes:u,result:d,onHide:s,onResize:l},{default:z(()=>[e.html?(G(),j(`div`,{key:0,innerHTML:e.finalContent},null,8,fu)):(G(),j(`div`,{key:1,textContent:q(e.finalContent)},null,8,pu))]),_:2},1032,[`class`,`popper-id`,`theme`,`shown`,`mounted`,`skip-transition`,`auto-hide`,`handle-resize`,`classes`,`result`,`onHide`,`onResize`])]),_:1},16,[`theme`,`target-nodes`,`popper-node`,`onApplyShow`,`onApplyHide`])}var hu=Wl(du,[[`render`,mu]]),gu=`v-popper--has-tooltip`;function _u(e,t){let n=e.placement;if(!n&&t)for(let e of bl)t[e]&&(n=e);return n||=hl(e.theme||`tooltip`,`placement`),n}function vu(e,t,n){let r,i=typeof t;return r=i===`string`?{content:t}:t&&i===`object`?t:{content:!1},r.placement=_u(r,n),r.targetNodes=()=>[e],r.referenceNode=()=>e,r}var yu,bu,xu=0;function Su(){if(yu)return;bu=B([]),yu=Oe({name:`VTooltipDirectiveApp`,setup(){return{directives:bu}},render(){return this.directives.map(e=>je(hu,{...e.options,shown:e.shown||e.options.shown,key:e.id}))},devtools:{hide:!0}});let e=document.createElement(`div`);document.body.appendChild(e),yu.mount(e)}function Cu(e,t,n){Su();let r=B(vu(e,t,n)),i=B(!1),a={id:xu++,options:r,shown:i};return bu.value.push(a),e.classList&&e.classList.add(gu),e.$_popper={options:r,item:a,show(){i.value=!0},hide(){i.value=!1}}}function wu(e){if(e.$_popper){let t=bu.value.indexOf(e.$_popper.item);t!==-1&&bu.value.splice(t,1),delete e.$_popper,delete e.$_popperOldShown,delete e.$_popperMountTarget}e.classList&&e.classList.remove(gu)}function Tu(e,{value:t,modifiers:n}){let r=vu(e,t,n);if(!r.content||hl(r.theme||`tooltip`,`disabled`))wu(e);else{let i;e.$_popper?(i=e.$_popper,i.options.value=r):i=Cu(e,t,n),typeof t.shown<`u`&&t.shown!==e.$_popperOldShown&&(e.$_popperOldShown=t.shown,t.shown?i.show():i.hide())}}var Eu={beforeMount:Tu,updated:Tu,beforeUnmount(e){wu(e)}},Du=J({__name:`ActionIconButton`,props:{as:{},to:{},href:{},icon:{},tooltip:{},active:{type:Boolean},disabled:{type:Boolean},label:{},compact:{type:Boolean},activeClass:{}},setup(e){let t=e,n=N(()=>t.as?t.as:t.href!=null||t.to!=null?`a`:`button`),r=N(()=>n.value===`a`),i=N(()=>n.value===`button`),a=N(()=>t.compact?`btn-icon-compact`:`btn-icon`),o=N(()=>t.compact?`w-auto! h-auto! p-[0.25em]`:`w-auto! h-auto! p-[0.5em]`),s=N(()=>t.active?t.activeClass||`color-active bg-active op100`:``);return(t,c)=>Ze((G(),F(he(n.value),{type:i.value?`button`:void 0,class:V([a.value,o.value,s.value,{"pointer-events-none op-mute":e.disabled&&!i.value}]),href:r.value?e.href??e.to:void 0,to:e.as&&e.to!=null?e.to:void 0,disabled:i.value?e.disabled:void 0,"aria-disabled":e.disabled&&!i.value||void 0,"aria-label":e.label??e.tooltip,"aria-pressed":e.active||void 0},{default:z(()=>[e.icon?(G(),j(`span`,{key:0,class:V(e.icon),"aria-hidden":`true`},null,2)):A(``,!0),Pe(t.$slots,`default`),Pe(t.$slots,`badge`)]),_:3},8,[`type`,`class`,`href`,`to`,`disabled`,`aria-disabled`,`aria-label`,`aria-pressed`])),[[U(Eu),e.tooltip]])}}),Ou={class:`devframes-view-assets-error absolute inset-0 flex items-center justify-center overflow-auto bg-base p6`},ku={class:`w-full max-w-100 flex flex-col items-center gap-2 text-center`},Au={class:`text-xs color-faint`},ju={class:`font-mono`},Mu={class:`mt-2 w-full flex items-center gap-1 border border-base rounded bg-secondary py1 pl2.5 pr1`},Nu={class:`flex-1 truncate text-left text-xs font-mono color-base`},Pu={class:`w-full max-h-24 overflow-auto text-left text-xs font-mono color-faint break-words`},Fu=J({__name:`ViewAssetsError`,props:{error:{}},emits:[`retry`],setup(e,{emit:t}){let n=e,r=t,i=N(()=>`npm install ${n.error.package}@${n.error.version}`),a=B(!1),o;async function s(){try{await navigator.clipboard.writeText(i.value),a.value=!0,clearTimeout(o),o=setTimeout(()=>a.value=!1,1500)}catch{}}return(t,n)=>(G(),j(`div`,Ou,[P(`div`,ku,[n[5]||=P(`div`,{class:`i-ph:cloud-warning-duotone text-3xl color-faint`},null,-1),n[6]||=P(`div`,{class:`text-sm color-muted`},` Failed to load assets remotely `,-1),P(`div`,Au,[n[2]||=M(` This tool's UI is published as `,-1),P(`span`,ju,q(e.error.package)+`@`+q(e.error.version),1),n[3]||=M(`. Install it locally, or enable network access to continue. `,-1)]),P(`div`,Mu,[P(`code`,Nu,q(i.value),1),I(Du,{class:`text-sm`,icon:a.value?`i-ph:check`:`i-ph:copy`,label:a.value?`Copied`:`Copy install command`,onClick:n[0]||=e=>s()},null,8,[`icon`,`label`])]),P(`div`,Pu,q(e.error.reason),1),I(ma,{class:`mt-2`,onClick:n[1]||=e=>r(`retry`)},{default:z(()=>[...n[4]||=[M(` Retry `,-1)]]),_:1})])]))}}),Iu={class:`devframes-view-iframe-loading absolute inset-0 flex flex-col items-center justify-center gap-2`},Lu=J({__name:`ViewIframeLoading`,setup(e){return(e,t)=>(G(),j(`div`,Iu,[...t[0]||=[P(`div`,{class:`i-ph:circle-notch-duotone animate-spin text-3xl color-faint`},null,-1),P(`div`,{class:`text-sm color-muted`},` Loading… `,-1)]]))}}),Ru={class:`w-full h-full flex flex-col`},zu={key:1,class:`flex items-center gap-1 px2 py1 rounded text-xs bg-amber/10 text-amber border border-amber/20 shrink-0`,title:`Cross-origin iframe`},Bu={class:`flex-1 flex items-center h-7 px-2.5 rounded bg-gray/5 border border-transparent hover:border-gray/10 focus-within:border-gray/15 transition-colors`},Vu=[`value`,`readonly`],Hu={key:0,class:`i-ph-circle-notch text-sm op40 ml-2 shrink-0 animate-spin`},Uu=40,Wu=J({__name:`ViewIframe`,props:{context:{},entry:{},panes:{},iframeStyle:{}},setup(e){let t=e,n=oe(t.context),r=N(()=>t.context.panel.store.mode===`edge`),i=N(()=>typeof t.entry.addressBar==`object`?t.entry.addressBar:void 0),a=N(()=>n.value.showIframeAddressBar||!!t.entry.addressBar),o=B(!0),s=B(!1),c=B(!1),l=B(null),u=N(()=>!l.value&&(o.value||s.value)),d=K(`viewFrame`),f=K(`urlInput`),p=B(t.entry.url),m=B(t.entry.url),h=B(!1),g=N(()=>t.entry.frameId??t.entry.id),_=N(()=>t.panes.get(g.value)?.iframe),v=N(()=>{try{return window.location.origin}catch{return``}}),y=N(()=>{try{return new URL(p.value).origin!==v.value}catch{return!0}}),b=N(()=>i.value?.back??!y.value),x=N(()=>i.value?.reload??!y.value),S=N(()=>i.value?.openExternal??!1),C=N(()=>{let e=re(p.value);if(y.value)return e;try{let t=new URL(e);return t.pathname+t.search+t.hash}catch{return e}});function w(e){let t=e.data;typeof t!=`object`||!t||t.type!==ce||e.source===_.value?.contentWindow&&(l.value={type:ce,package:String(t.package??``),version:String(t.version??``),reason:String(t.reason??``)})}function T(e){let t=_.value;if(!t)return;l.value=null;let n=e.trim();n&&!/^https?:\/\//i.test(n)&&(n=n.startsWith(`/`)?`${window.location.origin}${n}`:`http://${n}`),p.value=n,m.value=n,t.src=n,s.value=!0}function E(){h.value=!1,m.value!==p.value&&T(m.value)}function ee(){h.value=!0,m.value=p.value,Ae(()=>{f.value?.select()})}function te(){h.value=!1,m.value=p.value}function D(e){e.key===`Escape`&&(h.value=!1,m.value=p.value,f.value?.blur())}function ne(){try{_.value?.contentWindow?.history.back()}catch{}}function ie(){let e=_.value;if(!e)return;l.value=null,s.value=!0;let t=p.value;e.src=``,e.src=t,p.value=t,m.value=t}function ae(){try{let e=new URL(re(p.value));(e.protocol===`http:`||e.protocol===`https:`)&&window.open(e.href,`_blank`,`noopener,noreferrer`)}catch{}}let se,le;return Me(()=>{let e=t.panes.has(g.value),n=t.context.panel.consumeBootRoute?.(t.entry.id)??t.entry.url;!e&&n!==p.value&&(p.value=n,m.value=n);let i=t.panes.ensure(g.value,{src:n,style:{boxShadow:`none`,outline:`none`}}),u=i.iframe;le=ot({iframe:u,initial:p.value,onChange:e=>{p.value=e}}),e||(s.value=!0);let f=t.context.panel.session;$e(()=>{t.context.docks.selectedId===t.entry.id&&(f.selectedDockRoute=p.value)}),se=()=>{s.value=!1},u.addEventListener(`load`,se);let h=t.context.docks.getStateById(t.entry.id);h&&(h.domElements.iframe=u),$e(()=>{Object.assign(u.style,t.iframeStyle),a.value&&!r.value?(u.style.borderTopLeftRadius=`0px`,u.style.borderTopRightRadius=`0px`):(u.style.borderTopLeftRadius=``,u.style.borderTopRightRadius=``),r.value&&(u.style.borderRadius=`0px`,u.style.border=`none`)}),$e(()=>{c.value&&(l.value||s.value?i.hide():i.show())}),window.addEventListener(`message`,w),i.mount(d.value),o.value=!1,c.value=!0,Ae(()=>{i.update()})}),xe(()=>{window.removeEventListener(`message`,w),le?.(),le=void 0;let e=t.panes.get(g.value);e&&se&&e.iframe?.removeEventListener(`load`,se),e&&e.target===d.value&&e.unmount()}),(e,t)=>(G(),j(`div`,Ru,[a.value?(G(),j(`div`,{key:0,class:`flex-none px-2 w-full flex items-center gap-1 color-base border-base border-b`,style:R({height:`${Uu}px`})},[b.value?(G(),j(`button`,{key:0,class:`w-7 h-7 flex items-center justify-center rounded hover:bg-gray/15 transition-colors shrink-0`,title:`Back`,onClick:ne},[...t[1]||=[P(`div`,{class:`i-ph-caret-left op60 w-4.5 h-4.5`},null,-1)]])):A(``,!0),y.value?(G(),j(`div`,zu,[...t[2]||=[P(`div`,{class:`i-ph-globe text-sm`},null,-1),P(`span`,null,`Cross-Origin`,-1)]])):A(``,!0),x.value?(G(),j(`button`,{key:2,class:`w-7 h-7 flex items-center justify-center rounded hover:bg-gray/15 transition-colors shrink-0`,title:`Reload`,onClick:ie},[...t[3]||=[P(`div`,{class:`i-ph-arrow-clockwise op60 w-4.5 h-4.5`},null,-1)]])):A(``,!0),P(`div`,Bu,[P(`input`,{ref:`urlInput`,value:h.value?m.value:C.value,type:`text`,class:`flex-1 bg-transparent outline-none text-sm font-mono`,placeholder:`Enter URL...`,readonly:y.value,onInput:t[0]||=e=>m.value=e.target.value,onFocus:ee,onBlur:te,onKeydown:[D,ye(E,[`enter`])]},null,40,Vu),s.value?(G(),j(`div`,Hu)):A(``,!0)]),S.value?(G(),j(`button`,{key:3,class:`w-7 h-7 flex items-center justify-center rounded hover:bg-gray/15 transition-colors shrink-0`,title:`Open externally`,onClick:ae},[...t[4]||=[P(`div`,{class:`i-ph-arrow-square-out-duotone op60 w-4.5 h-4.5`},null,-1)]])):A(``,!0)],4)):A(``,!0),P(`div`,{ref_key:`viewFrame`,ref:d,class:`devframes-view-iframe relative w-full h-full flex-1 items-center justify-center`},[u.value?(G(),F(Lu,{key:0})):A(``,!0),l.value?(G(),F(Fu,{key:1,error:l.value,onRetry:ie},null,8,[`error`])):A(``,!0)],512)]))}}),Gu={class:`flex flex-col gap-4 items-center justify-center h-full relative`},Ku={class:`text-2xl font-bold`},qu={key:0,class:`flex flex-col gap-1 max-w-full w-64 items-start`},Ju=[`disabled`,`title`],Yu=[`value`],Xu={class:`min-h-16 max-w-full flex flex-col gap-2 items-center`},Zu={key:0,class:`max-w-full text-sm text-red-600 dark:text-red-400 text-center text-balance`},Qu={key:1,class:`inline-flex items-center gap-2 text-sm color-base op70`},$u={key:0,class:`i-ph-dots-three-outline-duotone animate-pulse flex-none`},ed={class:`truncate`},td=J({__name:`ViewLauncher`,props:{context:{},entry:{}},setup(e){let t=e,n=N(()=>t.entry.launcher.roots),r=B(n.value?.[0]?.value);Xe(n,e=>{let t=e?.[0];if(!t){r.value=void 0;return}e.some(e=>e.value===r.value)||(r.value=t.value)},{immediate:!0});function i(){let e=t.entry.launcher.command;e&&t.context.commands.execute(e,n.value?.length?{root:r.value}:void 0)}let a=N(()=>t.entry.launcher.status||`idle`),o=N(()=>t.entry.launcher.digest),s=N(()=>t.entry.launcher.terminalSessionId);function c(){s.value&&t.context.rpc.call(Ee.rpc.docksActivate,{dockId:p,params:{sessionId:s.value}})}let l={error:`i-ph-arrow-clockwise-duotone`,idle:`i-ph-rocket-launch-duotone`,loading:`i-ph-spinner-gap-duotone animate-spin`,success:`i-ph:check`},u=N(()=>t.entry.launcher.error),d=N(()=>a.value===`idle`?t.entry.launcher.buttonStart||`Launch`:a.value===`loading`?t.entry.launcher.buttonLoading||`Loading...`:a.value===`error`?`Retry`:a.value===`success`?`Success`:`UNKNOWN STATUS: ${a.value}`),f=N(()=>a.value===`idle`||a.value===`error`);return(t,p)=>(G(),j(`div`,Gu,[I(Ui,{icon:e.entry.launcher.icon||e.entry.icon,class:`w-10 h-10`},null,8,[`icon`]),P(`h1`,Ku,q(e.entry.launcher.title),1),P(`p`,null,q(e.entry.launcher.description),1),n.value?.length?(G(),j(`label`,qu,[p[1]||=P(`span`,{class:`text-xs op60`},`Launch root`,-1),Ze(P(`select`,{"onUpdate:modelValue":p[0]||=e=>r.value=e,disabled:a.value===`loading`,title:n.value.find(e=>e.value===r.value)?.description,class:`w-full px3 py2 text-sm rounded-lg bg-base color-base border border-base outline-none transition-all focus-visible:ring-3 focus-visible:ring-primary-500/30 disabled:op50 disabled:pointer-events-none`},[(G(!0),j(H,null,L(n.value,e=>(G(),j(`option`,{key:e.value,value:e.value},q(e.label),9,Yu))),128))],8,Ju),[[it,r.value]])])):A(``,!0),I(ma,{variant:`primary`,class:V([`min-w-40 justify-center`,a.value===`error`?`bg-error! border-error!`:``]),icon:l[a.value],loading:a.value===`loading`,disabled:!f.value,onClick:i},{default:z(()=>[M(q(d.value),1)]),_:1},8,[`class`,`icon`,`loading`,`disabled`]),P(`div`,Xu,[a.value===`error`&&u.value?(G(),j(`p`,Zu,q(u.value),1)):o.value?(G(),j(`div`,Qu,[a.value===`loading`?(G(),j(`div`,$u)):A(``,!0),P(`span`,ed,q(o.value),1)])):A(``,!0),s.value?(G(),F(ma,{key:2,variant:`text`,size:`sm`,icon:`i-ph-arrow-square-out-duotone`,onClick:c},{default:z(()=>[...p[2]||=[M(` View in Terminal `,-1)]]),_:1})):A(``,!0)])]))}}),nd={key:2},rd=J({__name:`ViewEntry`,props:{context:{},entry:{},panes:{},iframeStyle:{},divStyle:{}},setup(e){return(t,n)=>(G(),F(ge,null,{fallback:z(()=>[...n[0]||=[P(`div`,null,` Loading... `,-1)]]),default:z(()=>[e.entry.type===`~builtin`?(G(),j(H,{key:0},[e.entry.id===`~settings`?(G(),F(zs,{key:0,context:e.context,entry:e.entry},null,8,[`context`,`entry`])):e.entry.id===`~client-auth-notice`?(G(),F(_o,{key:1,context:e.context},null,8,[`context`])):(G(),j(`div`,nd,` Unknown builtin entry: `+q(e.entry),1))],64)):e.entry.type===`action`?(G(),j(H,{key:1},[],64)):e.entry.type===`iframe`?(G(),F(Wu,{key:2,context:e.context,entry:e.entry,panes:e.panes,"iframe-style":e.iframeStyle},null,8,[`context`,`entry`,`panes`,`iframe-style`])):e.entry.type===`custom-render`?(G(),F(Bs,{key:3,context:e.context,entry:e.entry,panes:e.panes,"div-style":e.divStyle},null,8,[`context`,`entry`,`panes`,`div-style`])):e.entry.type===`launcher`?(G(),F(td,{key:4,context:e.context,entry:e.entry},null,8,[`context`,`entry`])):(G(),F(qs,{key:5,context:e.context,entry:e.entry},null,8,[`context`,`entry`]))]),_:1}))}}),id=J({__name:`ColorSchemeRoot`,setup(e){return(e,t)=>(G(),j(`div`,{class:V([`devframes-color-root`,U(Be)?`dark`:`light`]),style:R({display:`contents`,colorScheme:U(Be)?`dark`:`light`})},[Pe(e.$slots,`default`)],6))}});function ad(e){return je(`button`,{class:`flex items-center gap-2 px3 py1.5 rounded hover:bg-active transition text-left`,onClick:e.action},[je(`div`,{class:`${e.icon} text-base op60`}),je(`span`,e.label)])}function od(e,t){let n=e.docks.settings,r=t.id;n.mutate(e=>{e.docksHidden.includes(r)||(e.docksHidden=[...e.docksHidden,r])}),e.docks.selected?.id===r&&e.docks.switchEntry(null),le(null)}function sd(e,t){let n=e.docks.getStateById(t.id)?.domElements.iframe;if(!n){le(null);return}let r=n.src;n.src=``,n.src=r,le(null)}function cd(e,t){return t.id!==`~settings`&&e.docks.entries.some(e=>e.id===t.id)}function ld(e){return e.type===`iframe`}function ud(e){let{context:t,entry:n,el:r,gap:i=6}=e,a=t.panel.store.mode===`edge`,o=[{label:`Hide`,icon:`i-ph-eye-slash-duotone`,action:()=>od(t,n),visible:cd(t,n)},{label:`Refresh`,icon:`i-ph-arrow-clockwise-duotone`,action:()=>sd(t,n),visible:ld(n)},{label:a?`Float Mode`:`Edge Mode`,icon:a?`i-ph-arrows-out-duotone`:`i-ph-square-half-bottom-duotone`,action:()=>{if(a){let e=t.panel.store;switch(e.position){case`bottom`:e.left=50,e.top=100;break;case`top`:e.left=50,e.top=0;break;case`left`:e.left=0,e.top=50;break;case`right`:e.left=100,e.top=50}e.mode=`float`}else t.panel.store.mode=`edge`;le(null)},visible:t.clientType===`embedded`},{label:`Popup`,icon:`i-ph-arrow-square-out-duotone`,action:()=>{le(null),se(t)},visible:ue()&&!ie().value&&t.clientType===`embedded`}].filter(e=>e.visible);o.length!==0&&le({el:r,gap:i,content:()=>je(`div`,{class:`flex flex-col text-sm min-w-36 mx--1`},o.map(ad))})}var dd=[`aria-label`],fd=J({__name:`DockEntry`,props:{context:{},dock:{},isAction:{type:Boolean},isSelected:{type:Boolean},isDimmed:{type:Boolean},isVertical:{type:Boolean},badge:{},badgeVariant:{},tooltip:{type:Boolean,default:!0}},setup(e){let t=e,n={info:{bg:`#3b82f6`,fg:`#eff6ff`},success:{bg:`#22c55e`,fg:`#f0fdf4`},warning:{bg:`#f59e0b`,fg:`#fffbeb`},danger:{bg:`#ef4444`,fg:`#fef2f2`}},r=N(()=>{if(!t.badgeVariant||t.badgeVariant==="default")return;let{bg:e,fg:r}=n[t.badgeVariant];return{backgroundColor:e,color:r}}),i=K(`button`);function a(){t.tooltip&&i.value&&w({content:t.dock.title,el:i.value})}function o(){t.tooltip&&w(null)}function s(e){if(!i.value||t.dock.id===`overflow`)return;e.preventDefault(),o();let n=t.context.docks.entries.find(e=>e.id===t.dock.id);n&&ud({context:t.context,entry:n,el:i.value,gap:6})}return Fe(`pointerdown`,()=>{t.tooltip&&w(null)}),(t,n)=>(G(),j(`div`,{key:e.dock.id,class:`relative group devframes-dock-entry`,onPointerenter:a,onPointerleave:o,onContextmenu:s},[P(`button`,{ref_key:`button`,ref:i,"aria-label":e.dock.title,class:V([[e.isVertical?`rotate-270`:``,e.isDimmed?`op50 saturate-0`:``,e.isSelected?`scale-120 text-primary`:``,e.isAction?`bg-[#8881] hover:bg-[#8882] rounded-full`:`rounded-xl`],`flex items-center justify-center p1.5 hover:bg-[#8881] hover:scale-110 transition-all duration-300 relative outline-none`])},[I(Ui,{icon:e.dock.icon,class:`w-5 h-5 select-none`},null,8,[`icon`]),e.badge?(G(),j(`div`,{key:0,class:V([`absolute top-0.5 right-0 text-0.6em px-1 rounded-full shadow`,r.value?``:`bg-gray-500 text-white`]),style:R(r.value)},q(e.badge),7)):A(``,!0)],10,dd)],32))}});function pd(e){return e?{"--devframe-primary":e}:void 0}var md={class:`truncate`},hd={key:0,class:`border-t border-base mx--2 my1`},gd=[`onClick`],_d={class:`truncate flex-1`},vd={key:0,class:`bg-gray-6 text-white text-0.6em px-1 rounded-full shadow`},yd={key:0,class:`px2 py1.5 op50 text-sm italic`},bd=J({__name:`DockGroupPopover`,props:{context:{},group:{},members:{},selectedId:{}},emits:[`select`],setup(e,{emit:t}){let n=e,r=t,i=N(()=>n.members.every(([,e])=>e.length===0));return(t,n)=>(G(),j(`div`,{class:V([`flex flex-col gap-0.5 min-w-44 max-w-64`,e.group.accentColor?`devframes-accent-scope`:``]),style:R(U(pd)(e.group.accentColor)),onMousemove:n[0]||=Te(()=>{},[`stop`])},[P(`div`,{class:V([`flex items-center gap-1.5 px4 mx--2 pt2 pb2.5 font-bold text-2.75 uppercase tracking-wide border-b border-base`,e.group.accentColor?`color-active`:``])},[I(Ui,{icon:e.group.icon,class:`w-4.5 h-4.5`},null,8,[`icon`]),P(`span`,md,q(e.group.title),1)],2),(G(!0),j(H,null,L(e.members,([t,n],i)=>(G(),j(H,{key:t},[i>0&&n.length?(G(),j(`div`,hd)):A(``,!0),(G(!0),j(H,null,L(n,t=>(G(),j(`button`,{key:t.id,class:V([`flex items-center gap-2 w-full px2 py1.5 rounded text-sm text-left transition`,e.selectedId===t.id?`text-primary bg-active`:`op80 hover:op100 hover:bg-active`]),onClick:e=>r(`select`,t)},[I(Ui,{icon:t.icon,class:`w-4.5 h-4.5 flex-none`},null,8,[`icon`]),P(`span`,_d,q(t.title),1),t.badge?(G(),j(`div`,vd,q(t.badge),1)):A(``,!0)],10,gd))),128))],64))),128)),i.value?(G(),j(`div`,yd,` No tools yet `)):A(``,!0)],38))}}),xd=J({__name:`DockGroupButton`,props:{context:{},group:{},isVertical:{type:Boolean},selected:{},dimInactive:{type:Boolean,default:!0}},emits:[`select`],setup(e,{emit:t}){let r=e,i=t,a=oe(r.context),o=N(()=>n(r.context.docks.entries,r.group.id,a.value,{whenContext:r.context.when.context})),s=N(()=>v(r.context.docks.entries,r.group.id,a.value,{whenContext:r.context.when.context})),l=N(()=>{let e=r.selected?.id;return!!e&&o.value.some(t=>t.id===e)}),u=K(`groupButton`),d=B(!1),f=ne();function p(){u.value&&(d.value=!0,S({el:u.value,content:()=>je(bd,{context:r.context,group:r.group,members:s.value,selectedId:r.selected?.id??null,onSelect:e=>{i(`select`,e),m()}})}))}function m(){d.value=!1,S(null)}function h(){d.value?m():p()}Ie(()=>f.value,e=>{d.value=e?.el===u.value},{debounce:1e3});function g(){if(l.value){m(),i(`select`,void 0);return}let e=c(r.context.docks.entries,r.group,r.context.panel.session.groupLastChildIds?.[r.group.id],r.context.when.context);if(e){m(),i(`select`,e);return}h()}return(t,n)=>(G(),j(`div`,{ref_key:`groupButton`,ref:u,class:V(e.group.accentColor?`devframes-accent-scope`:``),style:R(U(pd)(e.group.accentColor))},[I(fd,{context:e.context,dock:e.group,"is-vertical":e.isVertical,"is-selected":l.value||d.value,"is-dimmed":e.dimInactive&&e.selected?!l.value:!1,badge:e.group.badge,"badge-variant":e.group.badgeVariant,onClick:g},null,8,[`context`,`dock`,`is-vertical`,`is-selected`,`is-dimmed`,`badge`,`badge-variant`])],6))}}),Sd=J({__name:`DockEntries`,props:{context:{},entries:{},selected:{},isVertical:{type:Boolean},dimInactive:{type:Boolean,default:!0}},emits:[`select`],setup(t,{emit:r}){let i=t,a=r,{selected:s,isVertical:c,entries:l}=W(i),u=oe(i.context);function d(t){return!(t.type===`group`&&n(i.context.docks.entries,t.id,u.value,{whenContext:i.context.when.context}).length===0&&!o(i.context.docks.entries,t.id,t.defaultChildId,i.context.when.context)||t.when&&!e(t.when,i.context.when.context)||t.visibility&&!e(t.visibility,i.context.when.context))}function f(e){s.value?.id===e.id?a(`select`,void 0):a(`select`,e)}return(e,n)=>(G(!0),j(H,null,L(U(l),e=>(G(),j(H,{key:e.id},[d(e)?(G(),j(H,{key:0},[e.type===`group`?(G(),F(xd,{key:0,context:t.context,group:e,"is-vertical":U(c),selected:U(s),"dim-inactive":t.dimInactive,onSelect:n[0]||=e=>a(`select`,e)},null,8,[`context`,`group`,`is-vertical`,`selected`,`dim-inactive`])):(G(),F(fd,{key:1,context:t.context,dock:e,"is-action":e.type===`action`,"is-selected":U(s)?.id===e.id,"is-dimmed":t.dimInactive&&U(s)?U(s).id!==e.id:!1,"is-vertical":U(c),badge:e.badge,"badge-variant":e.badgeVariant,onClick:t=>f(e)},null,8,[`context`,`dock`,`is-action`,`is-selected`,`is-dimmed`,`is-vertical`,`badge`,`badge-variant`,`onClick`]))],64)):A(``,!0)],64))),128))}}),Cd=J({__name:`DockEntriesWithCategories`,props:{context:{},groups:{},selected:{},isVertical:{type:Boolean},rotate:{type:Boolean,default:!0},dimInactive:{type:Boolean,default:!0}},emits:[`select`],setup(e,{emit:t}){let n=t;return(t,r)=>(G(!0),j(H,null,L(e.groups,([i,a],o)=>(G(),j(H,{key:i},[o>0?Pe(t.$slots,`separator`,{category:i,index:o,isVertical:e.isVertical},()=>[P(`div`,{class:V([`border-base m1`,e.isVertical&&!e.rotate?`w-20px h-px border-b-1.5`:`h-20px w-px border-r-1.5`])},null,2)],void 0,0):A(``,!0),I(Sd,{context:e.context,entries:a,"is-vertical":e.isVertical&&e.rotate,selected:e.selected,"dim-inactive":e.dimInactive,onSelect:r[0]||=e=>n(`select`,e)},null,8,[`context`,`entries`,`is-vertical`,`selected`,`dim-inactive`])],64))),128))}}),wd={key:0,class:`w-8 h-px border-t border-base my0.5`},Td=[`aria-label`,`onPointerenter`,`onClick`],Ed={key:0,class:`absolute top-0.5 right-0.5 bg-gray-6 text-white text-0.6em px-0.5 rounded-full shadow leading-none`},Dd={class:`absolute top-0.5 right-0.5 bg-gray-6 text-white text-0.6em px-0.5 rounded-full shadow leading-none`},Od=34,kd=34,Ad=7,jd=53,Md=J({__name:`DockGroupSidebar`,props:{context:{},group:{},selectedId:{}},setup(e){let t=e,n=oe(t.context),r=N(()=>v(t.context.docks.entries,t.group.id,n.value,{whenContext:t.context.when.context})),i=K(`sidebar`),o=B(0);Xe(i,(e,t,n)=>{if(!e){o.value=0;return}let r=e.ownerDocument?.defaultView?.ResizeObserver??globalThis.ResizeObserver;if(!r)return;let i=new r(([e])=>{e&&(o.value=e.borderBoxSize?.[0]?.blockSize??e.contentRect.height)});i.observe(e),n(()=>i.disconnect())},{immediate:!0,flush:`post`});let s=N(()=>r.value.reduce((e,[,t])=>e+t.length,0)),c=N(()=>a({availableHeight:o.value,reservedHeight:jd,itemHeight:Od,dividerHeight:Ad,moreButtonHeight:kd,dividerCount:Math.max(0,r.value.filter(([,e])=>e.length).length-1),totalItems:s.value})),u=N(()=>l(r.value,c.value)),d=N(()=>u.value.visible),f=N(()=>u.value.overflow),p=N(()=>f.value.reduce((e,[,t])=>e+t.length,0)),m=N(()=>p.value>0),h=N(()=>p.value>9?`9+`:p.value.toString()),g=N(()=>!!t.selectedId&&f.value.some(([,e])=>e.some(e=>e.id===t.selectedId)));function _(e){t.context.docks.switchEntry(e)}function y(e,t){w({content:t,el:e.currentTarget})}function b(){w(null)}let x=K(`moreButton`),S=B(!1),C=T();function E(){x.value&&(S.value=!0,O({el:x.value,placement:`right`,content:()=>je(bd,{context:t.context,group:t.group,members:f.value,selectedId:t.selectedId,onSelect:e=>{_(e.id),ee()}})}))}function ee(){S.value=!1,O(null)}function te(){S.value?ee():E()}Ie(()=>C.value,e=>{S.value=e?.el===x.value},{debounce:1e3});let D=N(()=>g.value||S.value);return(t,n)=>(G(),j(`div`,{ref_key:`sidebar`,ref:i,class:V([`devframes-group-sidebar flex flex-col flex-none w-12 h-full border-r border-base of-y-hidden of-x-hidden select-none py1.5 items-center gap-0.5`,e.group.accentColor?`devframes-accent-scope`:``]),style:R(U(pd)(e.group.accentColor))},[P(`div`,{class:V([`flex items-center justify-center w-8 h-8 op60`,e.group.accentColor?`color-active`:``]),onPointerenter:n[0]||=t=>y(t,e.group.title),onPointerleave:b},[I(Ui,{icon:e.group.icon,class:`w-5 h-5 flex-none`},null,8,[`icon`])],34),n[2]||=P(`div`,{class:`w-8 h-px border-t border-base my0.5`},null,-1),(G(!0),j(H,null,L(d.value,([t,n],r)=>(G(),j(H,{key:t},[r>0&&n.length?(G(),j(`div`,wd)):A(``,!0),(G(!0),j(H,null,L(n,t=>(G(),j(`button`,{key:t.id,"aria-label":t.title,class:V([`relative flex items-center justify-center w-8 h-8 rounded-lg transition`,e.selectedId===t.id?`text-primary bg-active`:`op60 hover:op100 hover:bg-active`]),onPointerenter:e=>y(e,t.title),onPointerleave:b,onPointerdown:b,onClick:e=>_(t.id)},[I(Ui,{icon:t.icon,class:`w-5 h-5 flex-none`},null,8,[`icon`]),t.badge?(G(),j(`div`,Ed,q(t.badge),1)):A(``,!0)],42,Td))),128))],64))),128)),m.value?(G(),j(`button`,{key:0,ref_key:`moreButton`,ref:x,"aria-label":`Show more`,class:V([`relative flex items-center justify-center w-8 h-8 rounded-lg transition mt-auto flex-none`,D.value?`text-primary bg-active`:`op60 hover:op100 hover:bg-active`]),onPointerenter:n[1]||=e=>y(e,`Show more`),onPointerleave:b,onPointerdown:b,onClick:te},[I(Ui,{icon:`ph:dots-three-circle-duotone`,class:`w-5 h-5 flex-none`}),P(`div`,Dd,q(h.value),1)],34)):A(``,!0)],6))}}),Nd={key:0,class:`h-screen w-screen of-hidden`},Pd={key:1,class:`h-screen w-screen of-hidden grid cols-[max-content_1fr]`},Fd={class:`border-r border-base flex flex-col min-h-0`},Id={class:`p2 border-b border-base flex`},Ld={class:`transition duration-200 p2 of-y-auto`},Rd={class:`min-h-0 flex`},zd={class:`relative flex-1 min-w-0 min-h-0`},Bd=ve(J({__name:`DockStandalone`,props:{context:{}},setup(e){let t=e.context,n=K(`viewsContainer`),r=ft(n,t.panel,()=>dt(t.docks.selected)),i=pt(t,e=>{e||t.docks.switchEntry(null)});Xe(()=>t.docks.entries,()=>{t.docks.selectedId||=t.docks.entries[0]?.id??null},{immediate:!0});let a=N(()=>t.docks.groupedEntries),o=N(()=>me(t.docks.entries,t.docks.selected));function s(e){e&&t.docks.switchEntry(e)}return(e,c)=>(G(),F(id,null,{default:z(()=>[U(i)?(G(),j(`div`,Pd,[P(`div`,Fd,[P(`div`,Id,[I(ur,{class:`w-7 h-7 ma`})]),P(`div`,Ld,[I(Cd,{context:U(t),groups:a.value,"is-vertical":!1,selected:U(t).docks.selected,"dim-inactive":!1,onSelect:c[0]||=e=>s(e?.id)},{separator:z(()=>[...c[1]||=[P(`div`,{class:`border-base border-b w-full my-2`},null,-1)]]),_:1},8,[`context`,`groups`,`selected`])])]),P(`div`,Rd,[o.value?(G(),F(Md,{key:0,context:U(t),group:o.value,"selected-id":U(t).docks.selected?.id??null},null,8,[`context`,`group`,`selected-id`])):A(``,!0),P(`div`,zd,[U(t).docks.selected&&U(r)?(G(),F(rd,{key:U(t).docks.selected.id,entry:U(t).docks.selected,context:U(t),panes:U(r)},null,8,[`entry`,`context`,`panes`])):A(``,!0),P(`div`,{id:`devframes-views-container`,ref_key:`viewsContainer`,ref:n,class:`pointer-events-auto`},null,512)])])])):(G(),j(`div`,Nd,[I(_o,{context:U(t)},null,8,[`context`])])),I(ao),I(fa,{context:U(t)},null,8,[`context`]),I(qa,{context:U(t)},null,8,[`context`]),I(Sa)]),_:1}))}}),{shadowRoot:!0,styles:[ct]});customElements.get(`devframes-dock-standalone`)||customElements.define(`devframes-dock-standalone`,Bd);export{Bd as DockStandalone}; diff --git a/patches/README.md b/patches/README.md deleted file mode 100644 index 4a075f4..0000000 --- a/patches/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Temporary dependency patches - -`@devframes/hub-ui@0.9.10` backports [Devframe PR #375](https://github.com/devframes/devframe/pull/375), commit `6df25f3a`, to the pinned embedded and standalone browser bundles. It removes toasts when their messages disappear and preserves surviving entries during full reconciliation. It does not change notification behavior for updated messages. - -The patch changes only the corresponding message-feed logic in the published assets. The standalone asset is minified on one line, so its textual patch is large despite the small behavior change. No dependency versions or unrelated generated assets are changed. - -Remove the patch and its `patchedDependencies` entry when a compatible Devframe upgrade includes the fix. The Vite/DevTools upgrade remains separate. `tests/e2e/devframe-notifications.test.ts` exercises both installed browser bundles through the real hub, including updates, dismissal, intentional resurfacing, and incremental/full removal. - -pnpm patches apply to the consuming workspace; they are not inherited by applications installing published CDB packages. A host on hub-ui 0.9.10 needs this patch to receive the removal fix. Hosts remain responsible for their own dependency installation. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ab5692f..f53e9ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -58,33 +58,33 @@ catalogs: version: 4.11.4 devtools: '@devframes/hub': - specifier: 0.9.10 - version: 0.9.10 + specifier: 1.0.0 + version: 1.0.0 '@devframes/hub-ui': - specifier: 0.9.10 - version: 0.9.10 + specifier: 1.0.0 + version: 1.0.0 '@devframes/json-render': - specifier: 0.9.10 - version: 0.9.10 + specifier: 1.0.0 + version: 1.0.0 '@devframes/json-render-ui': - specifier: 0.9.10 - version: 0.9.10 + specifier: 1.0.0 + version: 1.0.0 '@vitejs/devtools-kit': - specifier: 0.6.1 - version: 0.6.1 + specifier: 0.7.5 + version: 0.7.5 devframe: - specifier: 0.9.10 - version: 0.9.10 + specifier: 1.0.0 + version: 1.0.0 vite: - specifier: 8.1.5 - version: 8.1.5 + specifier: 8.3.0 + version: 8.3.0 protocol: '@standard-schema/spec': specifier: 1.1.0 version: 1.1.0 devtools-protocol: - specifier: 0.0.1672245 - version: 0.0.1672245 + specifier: 0.0.1698617 + version: 0.0.1698617 zod: specifier: 4.4.3 version: 4.4.3 @@ -123,8 +123,8 @@ catalogs: version: 8.21.1 types: '@types/chrome': - specifier: 0.2.9 - version: 0.2.9 + specifier: 0.3.0 + version: 0.3.0 '@types/node': specifier: 26.1.2 version: 26.1.2 @@ -135,23 +135,16 @@ catalogs: specifier: 5.9.3 version: 5.9.3 -overrides: - '@vitejs/devtools-kit>@devframes/hub': 0.9.10 - '@vitejs/devtools-kit>@devframes/json-render': 0.9.10 - -patchedDependencies: - '@devframes/hub-ui@0.9.10': 52f9ce54ff28e6ee4b64e20e555b085f562f9ca932e4459a9032ff910dfce087 - importers: .: devDependencies: '@devframes/hub': specifier: catalog:devtools - version: 0.9.10(crossws@0.4.12(srvx@1.0.3))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)) + version: 1.0.0(crossws@0.4.12(srvx@1.0.3))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)) '@devframes/hub-ui': specifier: catalog:devtools - version: 0.9.10(patch_hash=52f9ce54ff28e6ee4b64e20e555b085f562f9ca932e4459a9032ff910dfce087)(@devframes/hub@0.9.10(crossws@0.4.12(srvx@1.0.3))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)))(@devframes/json-render@0.9.10(@devframes/hub@0.9.10(crossws@0.4.12(srvx@1.0.3))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)) + version: 1.0.0(@devframes/hub@1.0.0(crossws@0.4.12(srvx@1.0.3))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)))(@devframes/json-render@1.0.0(@devframes/hub@1.0.0(crossws@0.4.12(srvx@1.0.3))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)) '@dvcol/cdb': specifier: workspace:* version: link:packages/core @@ -190,13 +183,13 @@ importers: version: 3.4.2(prettier@3.9.6) '@types/chrome': specifier: catalog:types - version: 0.2.9 + version: 0.3.0 '@types/node': specifier: catalog:types version: 26.1.2 '@vitest/browser-playwright': specifier: catalog:testing - version: 4.1.10(playwright@1.62.1)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.10) + version: 4.1.10(playwright@1.62.1)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.10) '@vitest/coverage-v8': specifier: catalog:testing version: 4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10) @@ -208,7 +201,7 @@ importers: version: 15.0.5 devframe: specifier: catalog:devtools - version: 0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3) + version: 1.0.0(cac@7.0.0)(srvx@1.0.3) eslint: specifier: catalog:build version: 10.8.0(jiti@2.7.0)(supports-color@7.2.0) @@ -241,10 +234,10 @@ importers: version: 5.9.3 vite: specifier: catalog:devtools - version: 8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0) + version: 8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0) vitest: specifier: catalog:testing - version: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) examples/birpc: dependencies: @@ -283,13 +276,13 @@ importers: version: 2.0.0 '@vitejs/devtools-kit': specifier: catalog:devtools - version: 0.6.1(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(crossws@0.4.12(srvx@1.0.3))(srvx@1.0.3)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) + version: 0.7.5(cac@7.0.0)(crossws@0.4.12(srvx@1.0.3))(srvx@1.0.3)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) devframe: specifier: catalog:devtools - version: 0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3) + version: 1.0.0(cac@7.0.0)(srvx@1.0.3) vite: specifier: catalog:devtools - version: 8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0) + version: 8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0) examples/embedded: dependencies: @@ -311,7 +304,7 @@ importers: devDependencies: vite: specifier: catalog:devtools - version: 8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0) + version: 8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0) examples/mcp: dependencies: @@ -360,7 +353,7 @@ importers: version: 0.22.14(oxc-resolver@11.24.2)(publint@0.3.22)(typescript@5.9.3) vitest: specifier: catalog:testing - version: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) packages/birpc: dependencies: @@ -379,7 +372,7 @@ importers: version: 0.22.14(oxc-resolver@11.24.2)(publint@0.3.22)(typescript@5.9.3) vitest: specifier: catalog:testing - version: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) packages/broker: dependencies: @@ -395,7 +388,7 @@ importers: version: 0.22.14(oxc-resolver@11.24.2)(publint@0.3.22)(typescript@5.9.3) vitest: specifier: catalog:testing - version: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) packages/core: dependencies: @@ -414,25 +407,25 @@ importers: version: 3.0.1(ajv@8.20.0) devtools-protocol: specifier: catalog:protocol - version: 0.0.1672245 + version: 0.0.1698617 tsdown: specifier: catalog:build version: 0.22.14(oxc-resolver@11.24.2)(publint@0.3.22)(typescript@5.9.3) vitest: specifier: catalog:testing - version: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) packages/devframe: dependencies: '@devframes/hub': specifier: catalog:devtools - version: 0.9.10(crossws@0.4.12(srvx@1.0.3))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)) + version: 1.0.0(crossws@0.4.12(srvx@1.0.3))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)) '@devframes/json-render': specifier: catalog:devtools - version: 0.9.10(@devframes/hub@0.9.10(crossws@0.4.12(srvx@1.0.3))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)) + version: 1.0.0(@devframes/hub@1.0.0(crossws@0.4.12(srvx@1.0.3))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)) '@devframes/json-render-ui': specifier: catalog:devtools - version: 0.9.10(@devframes/hub@0.9.10(crossws@0.4.12(srvx@1.0.3))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)) + version: 1.0.0(@devframes/hub@1.0.0(crossws@0.4.12(srvx@1.0.3))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)) '@dvcol/cdb': specifier: workspace:^ version: link:../core @@ -444,17 +437,17 @@ importers: version: link:../extension devframe: specifier: catalog:devtools - version: 0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3) + version: 1.0.0(cac@7.0.0)(srvx@1.0.3) devDependencies: tsdown: specifier: catalog:build version: 0.22.14(oxc-resolver@11.24.2)(publint@0.3.22)(typescript@5.9.3) vite: specifier: catalog:devtools - version: 8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0) + version: 8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0) vitest: specifier: catalog:testing - version: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) packages/extension: dependencies: @@ -465,12 +458,15 @@ importers: specifier: workspace:^ version: link:../broker devDependencies: + '@dvcol/cdb-mcp': + specifier: workspace:^ + version: link:../mcp tsdown: specifier: catalog:build version: 0.22.14(oxc-resolver@11.24.2)(publint@0.3.22)(typescript@5.9.3) vitest: specifier: catalog:testing - version: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) packages/mcp: dependencies: @@ -498,7 +494,7 @@ importers: version: 0.22.14(oxc-resolver@11.24.2)(publint@0.3.22)(typescript@5.9.3) vitest: specifier: catalog:testing - version: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) packages/websocket: dependencies: @@ -517,7 +513,7 @@ importers: version: 0.22.14(oxc-resolver@11.24.2)(publint@0.3.22)(typescript@5.9.3) vitest: specifier: catalog:testing - version: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) + version: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) packages: @@ -668,37 +664,41 @@ packages: resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} engines: {node: '>=20.19.0'} - '@devframes/hub-ui@0.9.10': - resolution: {integrity: sha512-lO0J6UV7bwtLJv/+TrGry6LqAe4BucOj53JAmXX+kQumSPvTfjKfVvFHEk/Hiegxpz+M9zlSRMWzJ/wtpl0o4g==} + '@devframes/hub-ui@1.0.0': + resolution: {integrity: sha512-uSmkN4MBoudNgzOSgwGh8/NyBhdUN35Ye1M1eokqB+ETR+dMoj5SJDusvbIN/kbpZue2zCcy7920SOhrrt2yLw==} peerDependencies: - '@devframes/hub': 0.9.10 - '@devframes/json-render': 0.9.10 - devframe: 0.9.10 + '@devframes/hub': 1.0.0 + '@devframes/json-render': 1.0.0 + devframe: 1.0.0 peerDependenciesMeta: '@devframes/json-render': optional: true - '@devframes/hub@0.9.10': - resolution: {integrity: sha512-BFjO0iTEIazdNJbqvJkixV6d7DdUGaCpIHEsAZob2Lc59usOa8g4+jslRepbLhIraXm7SNUzP3w8Li9OBFHcdw==} + '@devframes/hub@1.0.0': + resolution: {integrity: sha512-L3NyTN5YDQ8LbKg5LjuovXVmsafTOxaMqZ0AHBIIfjK0PGhNWrIIydVrIm6VpoI+ARB/iS6k7sF15J80oBpo2Q==} peerDependencies: - devframe: 0.9.10 + '@devframes/agentic': 1.0.0 + devframe: 1.0.0 + peerDependenciesMeta: + '@devframes/agentic': + optional: true - '@devframes/json-render-ui@0.9.10': - resolution: {integrity: sha512-X/TQfmWINCstof3EjDjiwm0sBEhSWRmIhwt/TiwP/7jnAb+IkvFqrZSgjjQ/FVaHQMN0oZr1yutW6ih+7ka1og==} + '@devframes/json-render-ui@1.0.0': + resolution: {integrity: sha512-UJRcO+JkNp8mbKRcK+PoLBqtkzg5ED3kaVw2elIq37zvmgCFf3Hu1f34WcsJMiPr8mVsvoRiVAW2EQmWyLseUQ==} peerDependencies: - '@devframes/hub': 0.9.10 - devframe: 0.9.10 + '@devframes/hub': 1.0.0 + devframe: 1.0.0 peerDependenciesMeta: '@devframes/hub': optional: true devframe: optional: true - '@devframes/json-render@0.9.10': - resolution: {integrity: sha512-IWFJo1KEwsI4VcAEB3akuFLnqMopKsXQSIdOyB7ojnmHMua5X1eBSJkv7hMlyER4kFLCGrWl+U6Hz2aNArws9g==} + '@devframes/json-render@1.0.0': + resolution: {integrity: sha512-8BlYmhdIa473icCnnl14+erhXwLvq2Z5vhdhQWKX5mQ7mnh8psHdFr8lH9fVRYop2PO/O97OzZ+Lxjvlbnce/g==} peerDependencies: - '@devframes/hub': 0.9.10 - devframe: 0.9.10 + '@devframes/hub': 1.0.0 + devframe: 1.0.0 peerDependenciesMeta: '@devframes/hub': optional: true @@ -739,18 +739,12 @@ packages: oxlint: optional: true - '@emnapi/core@1.11.1': - resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} - '@emnapi/core@1.11.2': resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} '@emnapi/core@2.0.0-alpha.3': resolution: {integrity: sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==} - '@emnapi/runtime@1.11.1': - resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} - '@emnapi/runtime@1.11.2': resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} @@ -1039,15 +1033,15 @@ packages: cpu: [x64] os: [win32] - '@oxc-project/types@0.139.0': - resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} - '@oxc-project/types@0.142.0': resolution: {integrity: sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==} '@oxc-project/types@0.147.0': resolution: {integrity: sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==} + '@oxc-project/types@0.149.0': + resolution: {integrity: sha512-Efcc+iF0j3Bf67YjEqIqWXbX5XddXoK/Mw4K1/JuXwRCZ8N16VR7iT23nlCc9XrveFVh/E5Rqs2StT0V8v9LdA==} + '@oxc-resolver/binding-android-arm-eabi@11.24.2': resolution: {integrity: sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==} cpu: [arm] @@ -1292,10 +1286,10 @@ packages: '@quansync/fs@1.0.0': resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} - '@rolldown/binding-android-arm64@1.1.5': - resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} + '@rolldown/binding-android-arm-eabi@1.2.8': + resolution: {integrity: sha512-tN5aztYkKCte4i5SIrrz5yK/HMjEuCqCSCJa418jOV8tZ1cBY3YF2otxB1ktPxzsLA1BeTqwapK0bfjxNvHJVw==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] + cpu: [arm] os: [android] '@rolldown/binding-android-arm64@1.2.1': @@ -1304,11 +1298,11 @@ packages: cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.1.5': - resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} + '@rolldown/binding-android-arm64@1.2.8': + resolution: {integrity: sha512-dIYTWl9XprMUiQFoc55KUyk/oS8SKYH3zFl0LTR7RT0Xj4hgSVyuJcroH8JUu8RcpF8fTB6E0aOwCkZoYPcDSQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] - os: [darwin] + os: [android] '@rolldown/binding-darwin-arm64@1.2.1': resolution: {integrity: sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==} @@ -1316,10 +1310,10 @@ packages: cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.1.5': - resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} + '@rolldown/binding-darwin-arm64@1.2.8': + resolution: {integrity: sha512-PCSDQGXD2IyTEFrcgPyBM8jJuGmrbCMuoIOXdbEGVemruKACXoLQJrb+A45Z0L5t1RQkdfJprAYPkikbh7dzdA==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] + cpu: [arm64] os: [darwin] '@rolldown/binding-darwin-x64@1.2.1': @@ -1328,11 +1322,11 @@ packages: cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.1.5': - resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} + '@rolldown/binding-darwin-x64@1.2.8': + resolution: {integrity: sha512-Uk7lRsGhPFHVX/sAUC6D5H9Ol30dFHd6iquokll2th3LpdJ3F5CzQB+7DHn0Ri2mG+U7k2zXiPHDrwZenXhwSA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] - os: [freebsd] + os: [darwin] '@rolldown/binding-freebsd-x64@1.2.1': resolution: {integrity: sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==} @@ -1340,11 +1334,11 @@ packages: cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.1.5': - resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} + '@rolldown/binding-freebsd-x64@1.2.8': + resolution: {integrity: sha512-DjszaTEVogPqA5bYzsEeqDCQxbcp2fexQwKcRspYji2yzR68fCf+e4fx6kBSRDwX5/brZaHw/hWS9+A/+/w9sQ==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] + cpu: [x64] + os: [freebsd] '@rolldown/binding-linux-arm-gnueabihf@1.2.1': resolution: {integrity: sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==} @@ -1352,12 +1346,11 @@ packages: cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.1.5': - resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} + '@rolldown/binding-linux-arm-gnueabihf@1.2.8': + resolution: {integrity: sha512-zmwa7FTmdzB6aaEEuuls18H6Ap5JmJPSoPTuXixeJZV6tG40SyLkApQtz1g8ptZtiEKqj9OM0oNLPh1AgvE31Q==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] + cpu: [arm] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-gnu@1.2.1': resolution: {integrity: sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==} @@ -1366,12 +1359,12 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.1.5': - resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} + '@rolldown/binding-linux-arm64-gnu@1.2.8': + resolution: {integrity: sha512-KdYQDPHwJVnbFwdTGMgxsI9SqblBlz6STGM+w1We/d5B8OWWidYH0MwkU/uA1wM5fIpO2MkOVxXrNzzuZhw9ew==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.2.1': resolution: {integrity: sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==} @@ -1380,12 +1373,12 @@ packages: os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.1.5': - resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} + '@rolldown/binding-linux-arm64-musl@1.2.8': + resolution: {integrity: sha512-jFJTifHnNPY+yzOoNZQfSIysrVyXzEQPhPnOUjmD1bcQGHH6s7c8cViKWar8YplQImE5N9JRqMCLrM2CdxOrZA==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] + cpu: [arm64] os: [linux] - libc: [glibc] + libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.2.1': resolution: {integrity: sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==} @@ -1394,10 +1387,10 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.1.5': - resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} + '@rolldown/binding-linux-ppc64-gnu@1.2.8': + resolution: {integrity: sha512-FhiOziBDWPBjbcmRzfLyIJnaP7AVMFXT7YCXPjXxj7wKU3vx24RjrCNN/zjvVa+N2vVoHJwCoUBvsrN/DG3zIA==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] + cpu: [ppc64] os: [linux] libc: [glibc] @@ -1408,10 +1401,10 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.1.5': - resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} + '@rolldown/binding-linux-s390x-gnu@1.2.8': + resolution: {integrity: sha512-WnHfADMzOV2Y55wlx1hzzQnar/wDt/VdvWSD99r18Mz9ylNieIGOkRx3UV21h7m/eJvjySYJkO26VvGNFkwsIQ==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] + cpu: [s390x] os: [linux] libc: [glibc] @@ -1422,12 +1415,12 @@ packages: os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.1.5': - resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} + '@rolldown/binding-linux-x64-gnu@1.2.8': + resolution: {integrity: sha512-H9tRr5ibfXFVLxbPOseVewewFpl28zcEdjRDt2FTUZU7odxP0gEv1ki4/kGmcGOh78oRwZuuQllGLZ9zTJp84g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.2.1': resolution: {integrity: sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==} @@ -1436,11 +1429,12 @@ packages: os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.1.5': - resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} + '@rolldown/binding-linux-x64-musl@1.2.8': + resolution: {integrity: sha512-UefiqfM3D6IVNlZ8tSGs9+Ejjud2T+oxO0IHADU45Y+lyEjD2dVFyZHbkfX0LUb5Zugo/oIv1eCO/KVYhgYJYA==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] + cpu: [x64] + os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.2.1': resolution: {integrity: sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==} @@ -1448,35 +1442,36 @@ packages: cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.1.5': - resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} + '@rolldown/binding-openharmony-arm64@1.2.8': + resolution: {integrity: sha512-637Ke4kWSy6rp9cxQ9gMOXlxPgIw/c1beASV4M//3+9I4uwBVOOl74G+e3zyU3u19U7RkRl/HuewixZ/Z6+Rjg==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [wasm32] + cpu: [arm64] + os: [openharmony] '@rolldown/binding-wasm32-wasi@1.2.1': resolution: {integrity: sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==} engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} - '@rolldown/binding-win32-arm64-msvc@1.1.5': - resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} + '@rolldown/binding-win32-arm64-msvc@1.2.1': + resolution: {integrity: sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-arm64-msvc@1.2.1': - resolution: {integrity: sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==} + '@rolldown/binding-win32-arm64-msvc@1.2.8': + resolution: {integrity: sha512-xWBkPOF1Q9k/Gv1nQXnVdLxKu74jXppuOM4Z3mnypVUJJJwLsMl7hNJGRAUJoG8A5MgOI1ACKM+wBFxSJzKy4A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.1.5': - resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} + '@rolldown/binding-win32-x64-msvc@1.2.1': + resolution: {integrity: sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.2.1': - resolution: {integrity: sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==} + '@rolldown/binding-win32-x64-msvc@1.2.8': + resolution: {integrity: sha512-uz2ZvfgXbxqNwijjjbxrnvALwpyODDcgc1T1N8N3rf/DXKQmaFwmB4LX4yyjggpwN2obdQLb2rgirX5ffCWYng==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1533,8 +1528,8 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - '@types/chrome@0.2.9': - resolution: {integrity: sha512-6rXrDPkkWv4D2Q23HqT+iED4voODU5CpOSc2VFgKyecSFItuEsIBvz9CY6jUQ3dd+Q7zo1bnCE9pKRMtkYut5g==} + '@types/chrome@0.3.0': + resolution: {integrity: sha512-HPAMKyJwUnNJdVsxgypwvIStlwRhhaLnxgEwKIB99oOcffJrC4UMeD5xseVoBwDinSkr690DCBaAtSYdK6M1qw==} '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} @@ -1634,8 +1629,8 @@ packages: resolution: {integrity: sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitejs/devtools-kit@0.6.1': - resolution: {integrity: sha512-0+KY6TU5ukw/onaY3FdffSml/LZGuAauilbRqPYerS3GEkbgO3JKPYw4UsjlL5IahZ1AK9QpbPfoqhf5dhw8Og==} + '@vitejs/devtools-kit@0.7.5': + resolution: {integrity: sha512-OinQmY1I2CLQchDY+uOqF1HmnrXp/behOVeT0QdY1Smyn4Mlq12bUIZNFTweg+YybqwTluDzvdjCUrUAHYgSzw==} peerDependencies: vite: '*' @@ -1911,9 +1906,6 @@ packages: birpc@4.0.0: resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} - birpc@4.2.0: - resolution: {integrity: sha512-KxgKcZPfrtzJDDALHPguGpGJUrzdgpymyiQQgzFjWreHMOpWrnFNVREr5J48x2DBh8ZVioscrV1SBkDipGiX+Q==} - boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -2014,9 +2006,6 @@ packages: citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} - citty@0.2.2: - resolution: {integrity: sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==} - clean-regexp@1.0.0: resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} engines: {node: '>=4'} @@ -2128,14 +2117,14 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - devframe@0.9.10: - resolution: {integrity: sha512-pz/dYWrffiyXHeh/R10HhL3koamfQIr8Smu/JMDnxE9u6KaA60jM4Kh8Nd5el55CkDmjvbu+EFuApxP8gZHHWw==} + devframe@1.0.0: + resolution: {integrity: sha512-u4eeDBJ8Cxd3n19o/cT6SQcoEgASzL9dL51IkYpIDriTNDmR7WBv+o+dufQmabir10KcozoRZqgwvDi/riASAQ==} hasBin: true peerDependencies: - '@modelcontextprotocol/client': ^2.0.0 + '@devframes/agentic': 1.0.0 cac: ^7.0.0 peerDependenciesMeta: - '@modelcontextprotocol/client': + '@devframes/agentic': optional: true cac: optional: true @@ -2143,8 +2132,8 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - devtools-protocol@0.0.1672245: - resolution: {integrity: sha512-MYZkl0uU0s1ot3sSrKKTiLa3xWGDeJ+bpou238HTNHZIqAX43UYj+0kuTCcE88Fa5uHWDQOCBPIZ+X/cvPm+Yg==} + devtools-protocol@0.0.1698617: + resolution: {integrity: sha512-dxnsM+e9KpB2QxTjBla5BGppJwUENIPbin/qWbtkTw2Sn47qNmRpJedRXEF79TXUgN3NKFD3EdomYZXvznp78Q==} diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} @@ -3018,8 +3007,8 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nanoid@3.3.16: - resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + nanoid@3.3.19: + resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -3052,11 +3041,6 @@ packages: engines: {node: ^14.16.0 || >=16.10.0} hasBin: true - nypm@0.6.9: - resolution: {integrity: sha512-zxlE2yvSWZWmHcNdT3+5zV2lrCogeE9YOklHrR3dFjqutq5wO7GFDYLFDRXLsYnJzwvy/im9fYoxePvS0VTW0w==} - engines: {node: '>=18'} - hasBin: true - object-deep-merge@2.0.1: resolution: {integrity: sha512-aKttDKcU3pyZqKcCkDhsMn70WmZFG2JGDQLP9EcLyTSIFQRCPWLAmBZRLJnrVUrhPG1jETEEbfdgbNtJf1LyMg==} @@ -3195,8 +3179,8 @@ packages: resolution: {integrity: sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==} engines: {node: '>=4'} - postcss@8.5.25: - resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==} + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: @@ -3294,13 +3278,13 @@ packages: vue-tsc: optional: true - rolldown@1.1.5: - resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} + rolldown@1.2.1: + resolution: {integrity: sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rolldown@1.2.1: - resolution: {integrity: sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==} + rolldown@1.2.8: + resolution: {integrity: sha512-Z67nTmhZe7anqnM/EjI392w5i/ANUinjip7QYsOyN37oayduxt3ksdX0hf5OOamkAd53BiIHfbfSzfUmzKFQqQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -3434,6 +3418,10 @@ packages: resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} engines: {node: '>=18'} + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} + tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} @@ -3594,13 +3582,13 @@ packages: resolution: {integrity: sha512-sXMwN6DMHeouPfCxkxWkKAmxphWKEenHYY5H1nIBzU3PmDsmJp6kBXJdshjVdpMZuWCmL9SH7KFRx29AylpP6g==} engines: {node: '>=18.12.0'} - vite@8.1.5: - resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==} + vite@8.3.0: + resolution: {integrity: sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.3.0 + '@vitejs/devtools': ^0.7.1 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -3780,8 +3768,8 @@ packages: zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} - zod@4.5.4: - resolution: {integrity: sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==} + zod@4.6.5: + resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==} zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -3843,7 +3831,7 @@ snapshots: '@antfu/install-pkg@1.1.0': dependencies: package-manager-detector: 1.8.0 - tinyexec: 1.3.0 + tinyexec: 1.3.1 '@asamuzakjp/css-color@6.0.5': dependencies: @@ -3917,41 +3905,37 @@ snapshots: '@csstools/css-tokenizer@4.0.0': {} - '@devframes/hub-ui@0.9.10(patch_hash=52f9ce54ff28e6ee4b64e20e555b085f562f9ca932e4459a9032ff910dfce087)(@devframes/hub@0.9.10(crossws@0.4.12(srvx@1.0.3))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)))(@devframes/json-render@0.9.10(@devframes/hub@0.9.10(crossws@0.4.12(srvx@1.0.3))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3))': + '@devframes/hub-ui@1.0.0(@devframes/hub@1.0.0(crossws@0.4.12(srvx@1.0.3))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)))(@devframes/json-render@1.0.0(@devframes/hub@1.0.0(crossws@0.4.12(srvx@1.0.3))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3))': dependencies: - '@devframes/hub': 0.9.10(crossws@0.4.12(srvx@1.0.3))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)) - devframe: 0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3) + '@devframes/hub': 1.0.0(crossws@0.4.12(srvx@1.0.3))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)) + devframe: 1.0.0(cac@7.0.0)(srvx@1.0.3) optionalDependencies: - '@devframes/json-render': 0.9.10(@devframes/hub@0.9.10(crossws@0.4.12(srvx@1.0.3))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)) + '@devframes/json-render': 1.0.0(@devframes/hub@1.0.0(crossws@0.4.12(srvx@1.0.3))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)) - '@devframes/hub@0.9.10(crossws@0.4.12(srvx@1.0.3))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3))': + '@devframes/hub@1.0.0(crossws@0.4.12(srvx@1.0.3))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3))': dependencies: - '@standard-schema/spec': 1.1.0 destr: 2.0.5 - devframe: 0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3) + devframe: 1.0.0(cac@7.0.0)(srvx@1.0.3) h3: 2.0.1-rc.31(crossws@0.4.12(srvx@1.0.3)) pathe: 2.0.3 - perfect-debounce: 2.1.0 - tinyexec: 1.3.0 - ufo: 1.6.4 + tinyexec: 1.3.1 zigpty: 0.2.1 transitivePeerDependencies: - crossws - ocache - '@devframes/json-render-ui@0.9.10(@devframes/hub@0.9.10(crossws@0.4.12(srvx@1.0.3))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3))': + '@devframes/json-render-ui@1.0.0(@devframes/hub@1.0.0(crossws@0.4.12(srvx@1.0.3))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3))': optionalDependencies: - '@devframes/hub': 0.9.10(crossws@0.4.12(srvx@1.0.3))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)) - devframe: 0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3) + '@devframes/hub': 1.0.0(crossws@0.4.12(srvx@1.0.3))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)) + devframe: 1.0.0(cac@7.0.0)(srvx@1.0.3) - '@devframes/json-render@0.9.10(@devframes/hub@0.9.10(crossws@0.4.12(srvx@1.0.3))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3))': + '@devframes/json-render@1.0.0(@devframes/hub@1.0.0(crossws@0.4.12(srvx@1.0.3))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3))': dependencies: '@json-render/core': 0.20.0 - '@standard-schema/spec': 1.1.0 - devframe: 0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3) - zod: 4.5.4 + devframe: 1.0.0(cac@7.0.0)(srvx@1.0.3) + zod: 4.6.5 optionalDependencies: - '@devframes/hub': 0.9.10(crossws@0.4.12(srvx@1.0.3))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)) + '@devframes/hub': 1.0.0(crossws@0.4.12(srvx@1.0.3))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)) '@dprint/formatter@0.5.1': {} @@ -4000,12 +3984,6 @@ snapshots: optionalDependencies: eslint: 10.8.0(jiti@2.7.0)(supports-color@7.2.0) - '@emnapi/core@1.11.1': - dependencies: - '@emnapi/wasi-threads': 1.2.2 - tslib: 2.8.1 - optional: true - '@emnapi/core@1.11.2': dependencies: '@emnapi/wasi-threads': 1.2.2 @@ -4018,11 +3996,6 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.11.1': - dependencies: - tslib: 2.8.1 - optional: true - '@emnapi/runtime@1.11.2': dependencies: tslib: 2.8.1 @@ -4163,7 +4136,7 @@ snapshots: '@json-render/core@0.20.0': dependencies: - zod: 4.5.4 + zod: 4.6.5 '@modelcontextprotocol/client@2.0.0': dependencies: @@ -4191,13 +4164,6 @@ snapshots: '@modelcontextprotocol/core': 2.0.0 zod: 4.4.3 - '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': - dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 - '@tybys/wasm-util': 0.10.3 - optional: true - '@napi-rs/wasm-runtime@1.2.2(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': dependencies: '@emnapi/core': 1.11.2 @@ -4271,12 +4237,12 @@ snapshots: '@oxc-parser/binding-win32-x64-msvc@0.147.0': optional: true - '@oxc-project/types@0.139.0': {} - '@oxc-project/types@0.142.0': {} '@oxc-project/types@0.147.0': {} + '@oxc-project/types@0.149.0': {} + '@oxc-resolver/binding-android-arm-eabi@11.24.2': optional: true @@ -4412,83 +4378,79 @@ snapshots: dependencies: quansync: 1.0.0 - '@rolldown/binding-android-arm64@1.1.5': + '@rolldown/binding-android-arm-eabi@1.2.8': optional: true '@rolldown/binding-android-arm64@1.2.1': optional: true - '@rolldown/binding-darwin-arm64@1.1.5': + '@rolldown/binding-android-arm64@1.2.8': optional: true '@rolldown/binding-darwin-arm64@1.2.1': optional: true - '@rolldown/binding-darwin-x64@1.1.5': + '@rolldown/binding-darwin-arm64@1.2.8': optional: true '@rolldown/binding-darwin-x64@1.2.1': optional: true - '@rolldown/binding-freebsd-x64@1.1.5': + '@rolldown/binding-darwin-x64@1.2.8': optional: true '@rolldown/binding-freebsd-x64@1.2.1': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + '@rolldown/binding-freebsd-x64@1.2.8': optional: true '@rolldown/binding-linux-arm-gnueabihf@1.2.1': optional: true - '@rolldown/binding-linux-arm64-gnu@1.1.5': + '@rolldown/binding-linux-arm-gnueabihf@1.2.8': optional: true '@rolldown/binding-linux-arm64-gnu@1.2.1': optional: true - '@rolldown/binding-linux-arm64-musl@1.1.5': + '@rolldown/binding-linux-arm64-gnu@1.2.8': optional: true '@rolldown/binding-linux-arm64-musl@1.2.1': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.1.5': + '@rolldown/binding-linux-arm64-musl@1.2.8': optional: true '@rolldown/binding-linux-ppc64-gnu@1.2.1': optional: true - '@rolldown/binding-linux-s390x-gnu@1.1.5': + '@rolldown/binding-linux-ppc64-gnu@1.2.8': optional: true '@rolldown/binding-linux-s390x-gnu@1.2.1': optional: true - '@rolldown/binding-linux-x64-gnu@1.1.5': + '@rolldown/binding-linux-s390x-gnu@1.2.8': optional: true '@rolldown/binding-linux-x64-gnu@1.2.1': optional: true - '@rolldown/binding-linux-x64-musl@1.1.5': + '@rolldown/binding-linux-x64-gnu@1.2.8': optional: true '@rolldown/binding-linux-x64-musl@1.2.1': optional: true - '@rolldown/binding-openharmony-arm64@1.1.5': + '@rolldown/binding-linux-x64-musl@1.2.8': optional: true '@rolldown/binding-openharmony-arm64@1.2.1': optional: true - '@rolldown/binding-wasm32-wasi@1.1.5': - dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 - '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + '@rolldown/binding-openharmony-arm64@1.2.8': optional: true '@rolldown/binding-wasm32-wasi@1.2.1': @@ -4498,18 +4460,18 @@ snapshots: '@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3) optional: true - '@rolldown/binding-win32-arm64-msvc@1.1.5': - optional: true - '@rolldown/binding-win32-arm64-msvc@1.2.1': optional: true - '@rolldown/binding-win32-x64-msvc@1.1.5': + '@rolldown/binding-win32-arm64-msvc@1.2.8': optional: true '@rolldown/binding-win32-x64-msvc@1.2.1': optional: true + '@rolldown/binding-win32-x64-msvc@1.2.8': + optional: true + '@rolldown/pluginutils@1.0.1': {} '@sindresorhus/base62@1.0.0': {} @@ -4554,7 +4516,7 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 - '@types/chrome@0.2.9': + '@types/chrome@0.3.0': dependencies: '@types/filesystem': 0.0.36 '@types/har-format': 1.2.16 @@ -4686,46 +4648,45 @@ snapshots: '@typescript-eslint/types': 8.65.0 eslint-visitor-keys: 5.0.1 - '@vitejs/devtools-kit@0.6.1(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(crossws@0.4.12(srvx@1.0.3))(srvx@1.0.3)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0))': + '@vitejs/devtools-kit@0.7.5(cac@7.0.0)(crossws@0.4.12(srvx@1.0.3))(srvx@1.0.3)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0))': dependencies: - '@devframes/hub': 0.9.10(crossws@0.4.12(srvx@1.0.3))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)) - '@devframes/json-render': 0.9.10(@devframes/hub@0.9.10(crossws@0.4.12(srvx@1.0.3))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)))(devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3)) - devframe: 0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3) + '@devframes/hub': 1.0.0(crossws@0.4.12(srvx@1.0.3))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)) + '@devframes/json-render': 1.0.0(@devframes/hub@1.0.0(crossws@0.4.12(srvx@1.0.3))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)))(devframe@1.0.0(cac@7.0.0)(srvx@1.0.3)) + devframe: 1.0.0(cac@7.0.0)(srvx@1.0.3) local-pkg: 1.2.1 - mlly: 1.8.2 nostics: 1.2.0 - nypm: 0.6.9 - vite: 8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0) + package-manager-detector: 1.8.0 + vite: 8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0) transitivePeerDependencies: - - '@modelcontextprotocol/client' + - '@devframes/agentic' - cac - crossws - ocache - srvx - '@vitest/browser-playwright@4.1.10(playwright@1.62.1)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.10)': + '@vitest/browser-playwright@4.1.10(playwright@1.62.1)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.10)': dependencies: - '@vitest/browser': 4.1.10(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.10) - '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) + '@vitest/browser': 4.1.10(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.10) + '@vitest/mocker': 4.1.10(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) playwright: 1.62.1 tinyrainbow: 3.1.1 - vitest: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) + vitest: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser@4.1.10(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.10)': + '@vitest/browser@4.1.10(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.10)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) + '@vitest/mocker': 4.1.10(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) '@vitest/utils': 4.1.10 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.1 - vitest: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) + vitest: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) ws: 8.21.1 transitivePeerDependencies: - bufferutil @@ -4745,9 +4706,9 @@ snapshots: obug: 2.1.4 std-env: 4.2.0 tinyrainbow: 3.1.1 - vitest: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) + vitest: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) optionalDependencies: - '@vitest/browser': 4.1.10(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.10) + '@vitest/browser': 4.1.10(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.10) '@vitest/eslint-plugin@1.6.25(@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)(vitest@4.1.10)': dependencies: @@ -4757,7 +4718,7 @@ snapshots: optionalDependencies: '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) typescript: 5.9.3 - vitest: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) + vitest: 4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) transitivePeerDependencies: - supports-color @@ -4770,13 +4731,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.10(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0))': + '@vitest/mocker@4.1.10(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0) + vite: 8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0) '@vitest/pretty-format@4.1.10': dependencies: @@ -4937,8 +4898,6 @@ snapshots: birpc@4.0.0: {} - birpc@4.2.0: {} - boolbase@1.0.0: {} brace-expansion@5.0.9: @@ -5080,8 +5039,6 @@ snapshots: dependencies: consola: 3.4.2 - citty@0.2.2: {} - clean-regexp@1.0.0: dependencies: escape-string-regexp: 1.0.5 @@ -5167,19 +5124,13 @@ snapshots: detect-libc@2.1.2: {} - devframe@0.9.10(@modelcontextprotocol/client@2.0.0)(cac@7.0.0)(srvx@1.0.3): + devframe@1.0.0(cac@7.0.0)(srvx@1.0.3): dependencies: - '@modelcontextprotocol/server': 2.0.0 - '@standard-schema/spec': 1.1.0 - birpc: 4.2.0 crossws: 0.4.12(srvx@1.0.3) h3: 2.0.1-rc.31(crossws@0.4.12(srvx@1.0.3)) - mrmime: 2.0.1 nostics: 1.2.0 pathe: 2.0.3 - ufo: 1.6.4 optionalDependencies: - '@modelcontextprotocol/client': 2.0.0 cac: 7.0.0 transitivePeerDependencies: - ocache @@ -5189,7 +5140,7 @@ snapshots: dependencies: dequal: 2.0.3 - devtools-protocol@0.0.1672245: {} + devtools-protocol@0.0.1698617: {} diff-sequences@29.6.3: {} @@ -6242,7 +6193,7 @@ snapshots: ms@2.1.3: {} - nanoid@3.3.16: {} + nanoid@3.3.19: {} natural-compare@1.4.0: {} @@ -6269,12 +6220,6 @@ snapshots: tinyexec: 0.3.2 ufo: 1.6.4 - nypm@0.6.9: - dependencies: - citty: 0.2.2 - pathe: 2.0.3 - tinyexec: 1.3.0 - object-deep-merge@2.0.1: {} obug@2.1.4: {} @@ -6467,9 +6412,9 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss@8.5.25: + postcss@8.5.28: dependencies: - nanoid: 3.3.16 + nanoid: 3.3.19 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -6552,27 +6497,6 @@ snapshots: transitivePeerDependencies: - oxc-resolver - rolldown@1.1.5: - dependencies: - '@oxc-project/types': 0.139.0 - '@rolldown/pluginutils': 1.0.1 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.1.5 - '@rolldown/binding-darwin-arm64': 1.1.5 - '@rolldown/binding-darwin-x64': 1.1.5 - '@rolldown/binding-freebsd-x64': 1.1.5 - '@rolldown/binding-linux-arm-gnueabihf': 1.1.5 - '@rolldown/binding-linux-arm64-gnu': 1.1.5 - '@rolldown/binding-linux-arm64-musl': 1.1.5 - '@rolldown/binding-linux-ppc64-gnu': 1.1.5 - '@rolldown/binding-linux-s390x-gnu': 1.1.5 - '@rolldown/binding-linux-x64-gnu': 1.1.5 - '@rolldown/binding-linux-x64-musl': 1.1.5 - '@rolldown/binding-openharmony-arm64': 1.1.5 - '@rolldown/binding-wasm32-wasi': 1.1.5 - '@rolldown/binding-win32-arm64-msvc': 1.1.5 - '@rolldown/binding-win32-x64-msvc': 1.1.5 - rolldown@1.2.1: dependencies: '@oxc-project/types': 0.142.0 @@ -6594,6 +6518,27 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.2.1 '@rolldown/binding-win32-x64-msvc': 1.2.1 + rolldown@1.2.8: + dependencies: + '@oxc-project/types': 0.149.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm-eabi': 1.2.8 + '@rolldown/binding-android-arm64': 1.2.8 + '@rolldown/binding-darwin-arm64': 1.2.8 + '@rolldown/binding-darwin-x64': 1.2.8 + '@rolldown/binding-freebsd-x64': 1.2.8 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.8 + '@rolldown/binding-linux-arm64-gnu': 1.2.8 + '@rolldown/binding-linux-arm64-musl': 1.2.8 + '@rolldown/binding-linux-ppc64-gnu': 1.2.8 + '@rolldown/binding-linux-s390x-gnu': 1.2.8 + '@rolldown/binding-linux-x64-gnu': 1.2.8 + '@rolldown/binding-linux-x64-musl': 1.2.8 + '@rolldown/binding-openharmony-arm64': 1.2.8 + '@rolldown/binding-win32-arm64-msvc': 1.2.8 + '@rolldown/binding-win32-x64-msvc': 1.2.8 + rou3@0.9.2: {} run-applescript@7.1.0: {} @@ -6697,6 +6642,8 @@ snapshots: tinyexec@1.3.0: {} + tinyexec@1.3.1: {} + tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.7) @@ -6846,12 +6793,12 @@ snapshots: verkit@0.4.0: {} - vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0): + vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 - postcss: 8.5.25 - rolldown: 1.1.5 + postcss: 8.5.28 + rolldown: 1.2.8 tinyglobby: 0.2.17 optionalDependencies: '@types/node': 26.1.2 @@ -6859,10 +6806,10 @@ snapshots: jiti: 2.7.0 yaml: 2.9.0 - vitest@4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)): + vitest@4.1.10(@types/node@26.1.2)(@vitest/browser-playwright@4.1.10)(@vitest/coverage-v8@4.1.10)(jsdom@30.0.1)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.10 - '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) + '@vitest/mocker': 4.1.10(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.10 '@vitest/runner': 4.1.10 '@vitest/snapshot': 4.1.10 @@ -6879,11 +6826,11 @@ snapshots: tinyexec: 1.3.0 tinyglobby: 0.2.17 tinyrainbow: 3.1.1 - vite: 8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0) + vite: 8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 26.1.2 - '@vitest/browser-playwright': 4.1.10(playwright@1.62.1)(vite@8.1.5(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.10) + '@vitest/browser-playwright': 4.1.10(playwright@1.62.1)(vite@8.3.0(@types/node@26.1.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.10) '@vitest/coverage-v8': 4.1.10(@vitest/browser@4.1.10)(vitest@4.1.10) jsdom: 30.0.1 transitivePeerDependencies: @@ -7004,6 +6951,6 @@ snapshots: zod@4.4.3: {} - zod@4.5.4: {} + zod@4.6.5: {} zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7911204..5ae8c4b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,19 +5,22 @@ disallowWorkspaceCycles: true autoInstallPeers: false engineStrict: true saveWorkspaceProtocol: true +minimumReleaseAgeExclude: + - '@devframes/hub-ui@1.0.0' + - '@devframes/hub@1.0.0' + - '@devframes/json-render-ui@1.0.0' + - '@devframes/json-render@1.0.0' + - '@vitejs/devtools-kit@0.7.5' + - devframe@1.0.0 + - '@types/chrome@0.3.0' + - devtools-protocol@0.0.1698617 shellEmulator: true strictPeerDependencies: true trustPolicy: no-downgrade - packages: - packages/* - examples/* - tests/fixtures/* -overrides: - '@vitejs/devtools-kit>@devframes/hub': 0.9.10 - '@vitejs/devtools-kit>@devframes/json-render': 0.9.10 -patchedDependencies: - '@devframes/hub-ui@0.9.10': patches/@devframes__hub-ui@0.9.10.patch catalog: '@modelcontextprotocol/client': 2.0.0 '@modelcontextprotocol/node': 2.0.0 @@ -39,16 +42,16 @@ catalogs: tsdown: 0.22.14 turbo: 2.10.8 devtools: - '@devframes/hub': 0.9.10 - '@devframes/hub-ui': 0.9.10 - '@devframes/json-render': 0.9.10 - '@devframes/json-render-ui': 0.9.10 - '@vitejs/devtools-kit': 0.6.1 - devframe: 0.9.10 - vite: 8.1.5 + '@devframes/hub': 1.0.0 + '@devframes/hub-ui': 1.0.0 + '@devframes/json-render': 1.0.0 + '@devframes/json-render-ui': 1.0.0 + '@vitejs/devtools-kit': 0.7.5 + devframe: 1.0.0 + vite: 8.3.0 protocol: '@standard-schema/spec': 1.1.0 - devtools-protocol: 0.0.1672245 + devtools-protocol: 0.0.1698617 zod: 4.4.3 release: bumpp: 12.3.0 @@ -64,7 +67,7 @@ catalogs: transport: ws: 8.21.1 types: - '@types/chrome': 0.2.9 + '@types/chrome': 0.3.0 '@types/node': 26.1.2 '@types/ws': 8.18.1 typescript: 5.9.3 diff --git a/tests/browser/notification-renderer.test.ts b/tests/browser/notification-renderer.test.ts index d49eb64..fb01033 100644 --- a/tests/browser/notification-renderer.test.ts +++ b/tests/browser/notification-renderer.test.ts @@ -12,10 +12,84 @@ afterEach(() => { const request: BrokerRequest = { id: 'request', principalId: 'principal', principalLabel: '', level: 'interact', navigation: 'same-origin', state: 'pending', createdAt: 0, expiresAt: 2_000 }; const state: BrokerState = { revision: 1, providers: [], principals: [], requests: [request], grants: [], scopes: [], targets: [], leases: [] }; +it.each([ + ['card body', 'article'], + ['heading', 'h2'], + ['details', 'dl'], +])('invokes review once when clicking the pending request %s', async (_name, selector) => { + expect.assertions(2); + const review = vi.fn(); + const controller = createBrowserControlNotificationController({ onReview: review, onReject: async () => {}, onRevoke: async () => {} }); + controller.update({ ...state, requests: [{ ...request, expiresAt: null }] }); + const renderer = renderBrowserControlNotifications({ controller, container: document.body }); + const root = document.querySelector('section')!.shadowRoot!; + + root.querySelector(selector)!.click(); + + await vi.waitUntil(() => review.mock.calls.length === 1); + expect(review).toHaveBeenCalledOnce(); + expect(review).toHaveBeenCalledWith(expect.objectContaining({ id: request.id })); + renderer.dispose(); + controller.dispose(); +}); + +it('does not invoke review twice when clicking Accept', async () => { + expect.assertions(1); + const review = vi.fn(); + const controller = createBrowserControlNotificationController({ onReview: review, onReject: async () => {}, onRevoke: async () => {} }); + controller.update({ ...state, requests: [{ ...request, expiresAt: null }] }); + const renderer = renderBrowserControlNotifications({ controller, container: document.body, reviewLabel: () => 'Accept' }); + const root = document.querySelector('section')!.shadowRoot!; + + [...root.querySelectorAll('button')].find(button => button.textContent === 'Accept')!.click(); + + await vi.waitUntil(() => review.mock.calls.length === 1); + expect(review).toHaveBeenCalledOnce(); + renderer.dispose(); + controller.dispose(); +}); + +it('keeps Reject reject-only', async () => { + expect.assertions(3); + const review = vi.fn(); + const reject = vi.fn(); + const controller = createBrowserControlNotificationController({ onReview: review, onReject: reject, onRevoke: async () => {} }); + controller.update({ ...state, requests: [{ ...request, expiresAt: null }] }); + const renderer = renderBrowserControlNotifications({ controller, container: document.body }); + const root = document.querySelector('section')!.shadowRoot!; + + [...root.querySelectorAll('button')].find(button => button.textContent === 'Reject')!.click(); + + await vi.waitUntil(() => reject.mock.calls.length === 1); + expect(reject).toHaveBeenCalledOnce(); + expect(reject).toHaveBeenCalledWith(request.id); + expect(review).not.toHaveBeenCalled(); + renderer.dispose(); + controller.dispose(); +}); + +it('keeps Dismiss local-only', () => { + expect.assertions(3); + const review = vi.fn(); + const reject = vi.fn(); + const controller = createBrowserControlNotificationController({ onReview: review, onReject: reject, onRevoke: async () => {} }); + controller.update({ ...state, requests: [{ ...request, expiresAt: null }] }); + const renderer = renderBrowserControlNotifications({ controller, container: document.body }); + const root = document.querySelector('section')!.shadowRoot!; + + root.querySelector('.dismiss')!.click(); + + expect(root.querySelector('article')).toBeNull(); + expect(review).not.toHaveBeenCalled(); + expect(reject).not.toHaveBeenCalled(); + renderer.dispose(); + controller.dispose(); +}); + it.each(['Review request', 'Accept INTERACT'])('renders untrusted labels as text and delegates "%s" to the embedding application', async (label) => { expect.assertions(4); const review = vi.fn(); - const controller = createBrowserControlNotificationController({ onReview: review, onRevoke: async () => {} }); + const controller = createBrowserControlNotificationController({ onReview: review, onReject: async () => {}, onRevoke: async () => {} }); controller.update({ ...state, requests: [{ ...request, expiresAt: null }] }); const renderer = renderBrowserControlNotifications({ controller, container: document.body, branding: { title: 'Browser access' }, ...(label === 'Review request' ? {} : { reviewLabel: (request: BrokerRequest) => `Accept ${request.level.toUpperCase()}` }) }); const root = document.querySelector('section')!.shadowRoot!; @@ -31,7 +105,7 @@ it.each(['Review request', 'Accept INTERACT'])('renders untrusted labels as text it('preserves focused notification controls across equivalent broker publications', () => { expect.assertions(4); - const controller = createBrowserControlNotificationController({ onReview: async () => {}, onRevoke: async () => {} }); + const controller = createBrowserControlNotificationController({ onReview: async () => {}, onReject: async () => {}, onRevoke: async () => {} }); const pendingRequest = { ...request, expiresAt: null }; controller.update({ requests: [pendingRequest], grants: [] }); const renderer = renderBrowserControlNotifications({ controller, container: document.body }); @@ -55,7 +129,7 @@ it('preserves focused notification controls across equivalent broker publication it('changes theme and color mode without replacing focused controls or pending actions', async () => { expect.assertions(10); const pending = Promise.withResolvers(); - const controller = createBrowserControlNotificationController({ onReview: async () => pending.promise, onRevoke: async () => {} }); + const controller = createBrowserControlNotificationController({ onReview: async () => pending.promise, onReject: async () => {}, onRevoke: async () => {} }); controller.update({ requests: [{ ...request, expiresAt: null }], grants: [] }); const renderer = renderBrowserControlNotifications({ controller, container: document.body, theme: { light: { primary: 'green' } }, branding: { accent: 'purple' }, css: ':host { --cdb-primary: orange; }' }); const host = document.querySelector('section')!; diff --git a/tests/e2e/README.md b/tests/e2e/README.md index 02a2598..c8427fb 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -58,3 +58,11 @@ Both transports use the same normal closed-shadow fixture with three cross-origi Linux CI measurements establish a separate baseline from the historical macOS reports. Compare matching browser versions, fixtures and runner environments. Browser scheduling and transport overhead remain included; model time and application loading are excluded. Failed performance gates and unexplained stalls remain follow-ups for the experimental release. For a targeted diagnostic outside CI, the shared runner accepts `CDB_WORKFLOW_BENCHMARK_SAMPLES` from 5 to 50 and `CDB_WORKFLOW_PERFORMANCE_GATE=1`. Enable either `CDB_WEBSOCKET_BENCHMARK_OUTPUT` for `native-agent-benchmark.test.ts` or `CDB_DEVFRAME_BENCHMARK_OUTPUT` for `devframe-native.test.ts`. Enable `CDB_LIFECYCLE_DIAGNOSTICS_OUTPUT` separately for the latter's lifecycle matrix. Output paths are JSON files; do not commit generated measurements. + +## Native WebMCP + +`native-webmcp.test.ts` exercises native `document.modelContext` registration through the extension +debugger and public MCP tools. It requires a Chromium build exposing WebMCP, enables experimental +web platform features, and uses a trustworthy loopback page. Missing native support fails explicitly. +See [the WebMCP validation handoff](../../docs/webmcp-validation.md) before running it while another +session owns downstream TNR. diff --git a/tests/e2e/devframe-notification-ownership.test.ts b/tests/e2e/devframe-notification-ownership.test.ts new file mode 100644 index 0000000..09518e5 --- /dev/null +++ b/tests/e2e/devframe-notification-ownership.test.ts @@ -0,0 +1,86 @@ +import type { BrokerState } from '@dvcol/cdb-broker/contract'; +import type { BrowserControlPanelClient } from '@dvcol/cdb-devframe/panel'; + +import { mkdtemp, rm } from 'node:fs/promises'; +import { createServer } from 'node:http'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { createUi } from '@devframes/hub-ui'; +import { initHub } from '@devframes/hub/initiate'; +import { createCdbPanel } from '@dvcol/cdb-devframe'; +import { chromium } from 'playwright'; +import { expect, it } from 'vitest'; + +it.each(['embedded', 'standalone'] as const)('shares one approval notification between two %s clients with local actions', async (mode) => { + expect.assertions(12); + const directory = await mkdtemp(join(tmpdir(), 'cdb-notification-ownership-')); + const server = createServer(); + let origin = ''; + let state: BrokerState = { revision: 1, providers: [], principals: [], targets: [], grants: [], scopes: [], leases: [], requests: [] }; + const listeners = new Set<(state: BrokerState) => void>(); + const client: BrowserControlPanelClient = { + snapshot: () => state, + watch(listener) { + listeners.add(listener); + listener(state); + return () => listeners.delete(listener); + }, + revokeScope: async () => {}, + revokeGrant: async () => true, + disconnectProvider: async () => true, + }; + const panel = createCdbPanel({ client: () => client, approvalAction: 'accept' }); + const hub = initHub({ base: '/hub/', cwd: directory, origin: () => origin, auth: false, server, ui: createUi(), devframes: [panel.definition], configure(context) { + context.rpc.register({ name: 'devframes:plugin:messages:list', type: 'query', handler: (since?: number | null) => context.messages.listSince(since) }); + } }); + server.on('request', (request, response) => { + if (request.url !== '/app') return hub.nodeMiddleware(request, response); + response.setHeader('content-type', 'text/html'); + response.end('

Public CDB approval fixture

'); + }); + const browser = await chromium.launch({ channel: 'chromium', headless: true }); + try { + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); + const address = server.address(); + if (address === null || typeof address === 'string') throw new Error('Missing fixture address.'); + origin = `http://127.0.0.1:${address.port}`; + await hub.ready; + const context = await hub.context; + const first = await browser.newPage(); + const second = await browser.newPage(); + for (const page of [first, second]) { + await page.addInitScript(() => { + window.addEventListener('cdb:accept-request', (event) => { + document.documentElement.dataset.acceptedRequest = (event as CustomEvent<{ requestId: string }>).detail.requestId; + }); + }); + await page.goto(`${origin}${mode === 'embedded' ? '/app' : '/hub/'}`); + await expect.poll(async () => page.locator('html').getAttribute('data-cdb-notifications-ready'), { timeout: 5_000 }).toBe(''); + } + state = { ...state, requests: [{ id: 'public-request', principalId: 'public-agent', principalLabel: 'Public test agent', level: 'interact', navigation: 'same-origin', state: 'pending', createdAt: Date.now(), expiresAt: null }] }; + for (const listener of listeners) listener(state); + for (const page of [first, second]) { + await expect.poll(async () => page.getByRole('button', { name: 'Accept', exact: true }).count(), { timeout: 5_000 }).toBe(1); + } + expect(Array.from(context.messages.entries.values()).filter(message => message.message === 'Browser control requested')).toHaveLength(1); + await first.getByRole('button', { name: 'Accept', exact: true }).click(); + await expect.poll(async () => first.locator('html').getAttribute('data-accepted-request')).toBe('public-request'); + expect(await second.locator('html').getAttribute('data-accepted-request')).toBeNull(); + await first.close(); + expect(Array.from(context.messages.entries.values()).filter(message => message.message === 'Browser control requested')).toHaveLength(1); + await second.getByRole('button', { name: 'Accept', exact: true }).click(); + await expect.poll(async () => second.locator('html').getAttribute('data-accepted-request')).toBe('public-request'); + state = { ...state, requests: [] }; + for (const listener of listeners) listener(state); + await expect.poll(async () => second.getByText('Browser control requested', { exact: true }).count()).toBe(0); + expect(Array.from(context.messages.entries.values()).filter(message => message.message === 'Browser control requested')).toHaveLength(0); + expect(listeners.size).toBe(1); + } finally { + await browser.close(); + panel.dispose(); + await hub.close(); + await new Promise(resolve => server.close(() => resolve())); + await rm(directory, { recursive: true, force: true }); + } +}, 30_000); diff --git a/tests/e2e/devframe-notifications.test.ts b/tests/e2e/devframe-notifications.test.ts index 1f4e2c2..c27fe80 100644 --- a/tests/e2e/devframe-notifications.test.ts +++ b/tests/e2e/devframe-notifications.test.ts @@ -1,3 +1,5 @@ +import type { DevframeClientContext } from '@devframes/hub/client'; + import { mkdtemp, rm } from 'node:fs/promises'; import { createServer } from 'node:http'; import { tmpdir } from 'node:os'; @@ -8,8 +10,8 @@ import { initHub } from '@devframes/hub/initiate'; import { chromium } from 'playwright'; import { expect, it } from 'vitest'; -it.each(['embedded', 'standalone'] as const)('updates and removes notifications in the patched %s browser assets', async (mode) => { - expect.assertions(15); +it.each(['embedded', 'standalone'] as const)('updates and removes notifications in the %s browser assets', async (mode) => { + expect.assertions(mode === 'embedded' ? 16 : 15); const directory = await mkdtemp(join(tmpdir(), 'cdb-hub-notifications-')); const server = createServer(); let origin = ''; @@ -35,9 +37,24 @@ it.each(['embedded', 'standalone'] as const)('updates and removes notifications origin = `http://127.0.0.1:${address.port}`; await hub.ready; const context = await hub.context; + context.docks.register({ + id: 'vite', + type: 'iframe', + title: 'Vite', + icon: 'logos:vitejs', + url: '/fixture', + }); const page = await browser.newPage(); await page.goto(`${origin}${mode === 'embedded' ? '/app' : '/hub/'}`); await expect.poll(() => reads).toBeGreaterThan(0); + if (mode === 'embedded') { + await expect(page.evaluate(async () => { + const context = (globalThis as typeof globalThis & { __DEVFRAME_HUB_CLIENT_CONTEXT__: DevframeClientContext }) + .__DEVFRAME_HUB_CLIENT_CONTEXT__; + const switched = await context.docks.switchEntry('vite'); + return switched; + })).resolves.toBe(true); + } for (const reconcileFull of [false, true]) { full = reconcileFull; const message = await context.messages.info('CDB approval fixture', { notify: true, autoDismiss: false, description: 'One approved tab' }); diff --git a/tests/e2e/fixtures/authenticated-service-worker.ts b/tests/e2e/fixtures/authenticated-service-worker.ts index 8c11e67..8782aa9 100644 --- a/tests/e2e/fixtures/authenticated-service-worker.ts +++ b/tests/e2e/fixtures/authenticated-service-worker.ts @@ -1,6 +1,8 @@ import type { BrokerToAgentMessage, JsonObject, PublishedTarget } from '../../../packages/core/src/protocol.js'; import type { BrowserAgentConnection } from '../../../packages/websocket/src/browser.js'; +import { WebMcpError } from '@dvcol/cdb'; + import { createAgentRecovery, createBirpcAgentBootstrap, createIndexedDbPairingStore, createSelectedTabLifecycle, createSelectedTabPublisher, sendAgentHeartbeat } from '../../../packages/extension/src/index.js'; import { connectAgentWebSocket } from '../../../packages/websocket/src/browser.js'; @@ -422,7 +424,7 @@ bridgeTestGlobal.runPublishedTargetAgentTest = async (input) => { cancellations.set(command.operationId, abortController); void publisher.executeCommand(command, abortController.signal, lease).then( async value => connection.send({ kind: 'response', method: 'cdp.execute', protocolVersion: 1, requestId: executionRequest.requestId, result: { operationId: command.operationId, value } }), - async error => connection.send({ error: { code: 'CDP_COMMAND_FAILED', message: `${command.method}: ${error instanceof Error ? error.message : 'The debugger command failed.'}`, retryable: false }, kind: 'error', method: 'cdp.execute', protocolVersion: 1, requestId: executionRequest.requestId }), + async error => connection.send({ error: error instanceof WebMcpError ? { code: error.code, message: error.message, retryable: error.retryable } : { code: 'CDP_COMMAND_FAILED', message: `${command.method}: ${error instanceof Error ? error.message : 'The debugger command failed.'}`, retryable: false }, kind: 'error', method: 'cdp.execute', protocolVersion: 1, requestId: executionRequest.requestId }), ).finally(() => cancellations.delete(command.operationId)); }); }; diff --git a/tests/e2e/fixtures/native-mcp-harness.ts b/tests/e2e/fixtures/native-mcp-harness.ts index cce74e0..2b25d05 100644 --- a/tests/e2e/fixtures/native-mcp-harness.ts +++ b/tests/e2e/fixtures/native-mcp-harness.ts @@ -40,7 +40,7 @@ export interface NativeMcpHarness { } /** Exercises the public extension, authenticated transports, broker, and native MCP catalogue. */ -export async function createNativeMcpHarness(options: { readonly frames?: 'cross-origin' | 'same-origin'; readonly inlineResultBytes?: number; readonly mcpModule?: string; readonly profile?: DeepDomProfile; readonly shadow?: 'closed' | 'mixed' | 'open' } = {}): Promise { +export async function createNativeMcpHarness(options: { readonly browserArguments?: readonly string[]; readonly frames?: 'cross-origin' | 'same-origin'; readonly inlineResultBytes?: number; readonly mcpModule?: string; readonly profile?: DeepDomProfile; readonly shadow?: 'closed' | 'mixed' | 'open' } = {}): Promise { const cleanups: Array<() => Promise> = []; const close = async (): Promise => { const errors: unknown[] = []; @@ -102,7 +102,7 @@ export async function createNativeMcpHarness(options: { readonly frames?: 'cross version: '0.0.0', })); const context = await chromium.launchPersistentContext(profileDirectory, { - args: [`--disable-extensions-except=${extensionDirectory}`, `--load-extension=${extensionDirectory}`, '--site-per-process', '--host-resolver-rules=MAP *.test 127.0.0.1', '--no-proxy-server'], + args: [`--disable-extensions-except=${extensionDirectory}`, `--load-extension=${extensionDirectory}`, '--site-per-process', '--host-resolver-rules=MAP *.test 127.0.0.1', '--no-proxy-server', ...options.browserArguments ?? []], channel: 'chromium', headless: true, viewport: { height: 900, width: 1280 }, diff --git a/tests/e2e/native-webmcp.test.ts b/tests/e2e/native-webmcp.test.ts new file mode 100644 index 0000000..6cf6c8f --- /dev/null +++ b/tests/e2e/native-webmcp.test.ts @@ -0,0 +1,71 @@ +import type { WebMcpToolList, WebMcpToolResult } from '@dvcol/cdb'; + +import type { NativeMcpHarness } from './fixtures/native-mcp-harness.js'; + +import { afterEach, expect, it } from 'vitest'; + +import { createNativeMcpHarness, toolText } from './fixtures/native-mcp-harness.js'; + +interface NativeModelContext { + registerTool: (tool: { + name: string; + description: string; + inputSchema: Record; + execute: (input: { value?: string }) => Promise; + }, options: { signal: AbortSignal }) => Promise; +} + +let harness: NativeMcpHarness | undefined; +afterEach(async () => { + await harness?.close(); + harness = undefined; +}); + +/** Native registration is fixture setup; every agent operation uses the public MCP transport. */ +it('discovers and invokes a native main-document tool through the real extension debugger', async () => { + expect.assertions(9); + harness = await createNativeMcpHarness({ frames: 'same-origin', browserArguments: ['--enable-experimental-web-platform-features'] }); + const secureFixture = new URL(harness.fixtureUrl); + secureFixture.hostname = '127.0.0.1'; + await harness.page.goto(secureFixture.href); + await harness.page.evaluate(async () => { + const modelContext = (document as Document & { modelContext?: NativeModelContext }).modelContext; + if (modelContext === undefined) throw new Error('This Chromium build does not expose native document.modelContext.'); + await modelContext.registerTool({ + name: 'set_fixture_value', + description: 'Set the fixture value.', + inputSchema: { type: 'object', properties: { value: { type: 'string' } }, required: ['value'] }, + async execute(input) { + document.documentElement.dataset.webmcpValue = input.value; + return JSON.stringify({ value: input.value }); + }, + }, { signal: new AbortController().signal }); + const iframe = document.createElement('iframe'); + iframe.srcdoc = 'WebMCP child'; + const loaded = new Promise((resolve) => { + iframe.onload = () => resolve(); + }); + document.body.append(iframe); + await loaded; + const childContext = (iframe.contentDocument as Document & { modelContext: NativeModelContext }).modelContext; + await childContext.registerTool({ name: 'child_only', description: 'Iframe tool', inputSchema: { type: 'object' }, execute: async () => 'child' }, { signal: new AbortController().signal }); + }); + + const listed = await harness.mcpClient.callTool({ name: 'browser.list_webmcp_tools', arguments: { targetRef: harness.targetRef } }); + expect(listed.isError, toolText(listed)).toBeUndefined(); + const catalogue = JSON.parse(toolText(listed)) as WebMcpToolList; + expect(catalogue.tools.map(tool => tool.name)).toEqual(['set_fixture_value']); + const invoked = await harness.mcpClient.callTool({ name: 'browser.invoke_webmcp_tools', arguments: { targetRef: harness.targetRef, toolRef: catalogue.tools[0]!.toolRef, input: { value: 'native proof' } } }); + expect(invoked.isError, toolText(invoked)).toBeUndefined(); + expect(JSON.parse(toolText(invoked)) as WebMcpToolResult).toEqual({ status: 'completed', output: { value: 'native proof' } }); + expect(await harness.page.locator('html').getAttribute('data-webmcp-value')).toBe('native proof'); + + const exported = await harness.mcpClient.listTools(); + expect(exported.tools.some(tool => tool.name === 'set_fixture_value')).toBe(false); + await harness.page.reload(); + const stale = await harness.mcpClient.callTool({ name: 'browser.invoke_webmcp_tools', arguments: { targetRef: harness.targetRef, toolRef: catalogue.tools[0]!.toolRef, input: { value: 'replay' } } }); + expect(toolText(stale)).toContain('WEBMCP_TOOL_STALE'); + const empty = await harness.mcpClient.callTool({ name: 'browser.list_webmcp_tools', arguments: { targetRef: harness.targetRef } }); + expect(empty.isError, toolText(empty)).toBeUndefined(); + expect((JSON.parse(toolText(empty)) as WebMcpToolList).tools).toEqual([]); +}, 90_000);