diff --git a/.changeset/prefer-wired-handles.md b/.changeset/prefer-wired-handles.md new file mode 100644 index 0000000..fd965ac --- /dev/null +++ b/.changeset/prefer-wired-handles.md @@ -0,0 +1,6 @@ +--- +"@stainless-code/vue-layers": patch +"@stainless-code/svelte-layers": patch +--- + +Prefer wired handles (`useLayer` / `createLayer`) in Confirm docs and fire-and-forget skill examples; bag-form `client.open` remains a valid escape hatch. diff --git a/apps/docs/content/adapters/angular.mdx b/apps/docs/content/adapters/angular.mdx index 3190fa7..23563d8 100644 --- a/apps/docs/content/adapters/angular.mdx +++ b/apps/docs/content/adapters/angular.mdx @@ -64,7 +64,7 @@ import { layerOptions, provideLayerClient, renderStack, - useLayerClient, + injectLayer, type LayerComponentProps, } from "@stainless-code/angular-layers"; @@ -98,7 +98,7 @@ const confirm = layerOptions({ template: ``, }) export class AppComponent { - private client = useLayerClient(); + private c = injectLayer(confirm); private vcr = inject(ViewContainerRef); constructor() { @@ -106,7 +106,7 @@ export class AppComponent { } async remove() { - await this.client.open({ ...confirm, payload: { title: "Remove item?" } }); + await this.c.open({ title: "Remove item?" }); } } ``` diff --git a/apps/docs/content/adapters/preact.mdx b/apps/docs/content/adapters/preact.mdx index 3e3b663..6d28718 100644 --- a/apps/docs/content/adapters/preact.mdx +++ b/apps/docs/content/adapters/preact.mdx @@ -45,9 +45,7 @@ function App() { ```tsx import { layerOptions, - StackProvider, - StackOutlet, - useLayerClient, + useLayer, type LayerComponentProps, } from "@stainless-code/preact-layers"; @@ -74,12 +72,10 @@ const confirm = layerOptions({ }); function RemoveButton() { - const client = useLayerClient(); + const c = useLayer(confirm); return ( diff --git a/apps/docs/content/adapters/react.mdx b/apps/docs/content/adapters/react.mdx index f691bd9..fe6852b 100644 --- a/apps/docs/content/adapters/react.mdx +++ b/apps/docs/content/adapters/react.mdx @@ -44,9 +44,7 @@ Same pattern across adapters — declare, mount, open: ```tsx import { layerOptions, - StackOutlet, - StackProvider, - useLayerClient, + useLayer, type LayerComponentProps, } from "@stainless-code/react-layers"; @@ -73,12 +71,10 @@ const confirm = layerOptions({ }); function RemoveButton() { - const client = useLayerClient(); + const c = useLayer(confirm); return ( diff --git a/apps/docs/content/adapters/solid.mdx b/apps/docs/content/adapters/solid.mdx index 24fa869..a857776 100644 --- a/apps/docs/content/adapters/solid.mdx +++ b/apps/docs/content/adapters/solid.mdx @@ -54,7 +54,7 @@ import { LayerClientContext, LayerClient, StackOutlet, - useLayerClient, + useLayer, type LayerComponentProps, } from "@stainless-code/solid-layers"; @@ -80,12 +80,10 @@ const confirm = layerOptions({ const client = new LayerClient(); function RemoveButton() { - const c = useLayerClient(); + const c = useLayer(confirm); return ( diff --git a/apps/docs/content/adapters/svelte/runes.mdx b/apps/docs/content/adapters/svelte/runes.mdx index 016035e..be00f73 100644 --- a/apps/docs/content/adapters/svelte/runes.mdx +++ b/apps/docs/content/adapters/svelte/runes.mdx @@ -65,9 +65,9 @@ Import client/stack APIs from **one** entry only. Mixing this package with `@sta ```svelte diff --git a/apps/docs/content/adapters/svelte/store.mdx b/apps/docs/content/adapters/svelte/store.mdx index 0c5abde..c13d2cb 100644 --- a/apps/docs/content/adapters/svelte/store.mdx +++ b/apps/docs/content/adapters/svelte/store.mdx @@ -69,6 +69,7 @@ Import client/stack APIs from **one** entry only. Mixing `@stainless-code/svelte diff --git a/apps/docs/content/adapters/vue.mdx b/apps/docs/content/adapters/vue.mdx index e9205d9..7d245cf 100644 --- a/apps/docs/content/adapters/vue.mdx +++ b/apps/docs/content/adapters/vue.mdx @@ -49,7 +49,7 @@ import { layerOptions, provideLayerClient, StackOutlet, - useLayerClient, + useLayer, type LayerComponentProps, } from "@stainless-code/vue-layers"; @@ -79,10 +79,10 @@ const confirm = layerOptions({ }); provideLayerClient(); -const client = useLayerClient(); +const c = useLayer(confirm); async function remove() { - await client.open({ ...confirm, payload: { title: "Remove item?" } }); + await c.open({ title: "Remove item?" }); } diff --git a/apps/docs/content/concepts/identity-and-types.mdx b/apps/docs/content/concepts/identity-and-types.mdx index 7758808..7b70369 100644 --- a/apps/docs/content/concepts/identity-and-types.mdx +++ b/apps/docs/content/concepts/identity-and-types.mdx @@ -5,7 +5,7 @@ search: tags: ["concept"] --- -Two opens with the same `key` share **logical identity**; each still gets a unique instance id (**physical identity**). `DataTag` branding carries the response type through `await client.open(...)`, so no explicit generic is needed. +Two opens with the same `key` share **logical identity**; each still gets a unique instance id (**physical identity**). `DataTag` branding carries the response type through `await confirm.open(...)` (wired handle or bag-form), so no explicit generic is needed. ## Key vs instance id @@ -20,7 +20,7 @@ Two opens with the same `key` share **logical identity**; each still gets a uniq ## Wired handle path -Besides `client.open({ …layerOptions, payload })`, bind a declaration with `createLayer(options, client)` (headless) or `useLayer(options)` (adapter). Payload-only `open`/`upsert` infer `R` from the same `DataTag` on `options.key`: +Bind a declaration with `useLayer(options)` (adapter) or `createLayer(options, client)` (headless). Payload-only `open`/`upsert` infer `R` from the same `DataTag` on `options.key`: ```tsx import { useLayer } from "@stainless-code/react-layers"; @@ -32,12 +32,14 @@ const ok = await confirmLayer.open({ title: "Remove?" }); Pass `{ id: state.id }` on `dismiss`/`update`/`cancelQueued` when targeting a specific instance — `current` on the handle is the caller's last `open`, not an implicit default for stack ops. +Bag-form `client.open({ …layerOptions, payload })` stays valid — see [Migration](/reference/migration). + ## DataTag — typed `await open` -`layerOptions` brands its `key` with a `DataTag`, so `LayerClient.open` infers the response type with no explicit generic: +`layerOptions` brands its `key` with a `DataTag`, so handle `open` and `LayerClient.open` infer the response type with no explicit generic: ```ts -import { LayerClient, layerOptions } from "@stainless-code/layers"; +import { LayerClient, createLayer, layerOptions } from "@stainless-code/layers"; type ConfirmPayload = { title: string }; type ConfirmResponse = boolean; @@ -49,7 +51,8 @@ const confirm = layerOptions({ key: ["confirm", "remove"], }); -const ok = await client.open({ ...confirm, payload: { title: "Remove?" } }); +const c = createLayer(confirm, client); +const ok = await c.open({ title: "Remove?" }); // ^? boolean ``` @@ -72,12 +75,15 @@ There is **no** global key→response registry. Colocated `layerOptions` / `laye The response type `R` defaults to `void` (fire-and-forget), and `payload` is omittable when the layer needs no input: ```ts +import { createLayer } from "@stainless-code/layers"; + const about = layerOptions({ stack: "modal", key: ["about"], }); -void client.open(about); // fire-and-forget +const c = createLayer(about, client); +void c.open(); // fire-and-forget ``` ## Register and DefaultLayerError diff --git a/apps/docs/content/concepts/overview.mdx b/apps/docs/content/concepts/overview.mdx index 592efd8..32a92f7 100644 --- a/apps/docs/content/concepts/overview.mdx +++ b/apps/docs/content/concepts/overview.mdx @@ -5,7 +5,7 @@ search: tags: ["concept"] --- -A confirm, toast, or drawer rarely stays where it started — prop-drilled `isOpen` and threaded callbacks don't scale. Layers moves that into a **headless client**: declare once, mount an outlet, `await client.open(...)` (or a wired handle) from any call site. Zero-dep core; thin adapters bind each framework's reactivity. +A confirm, toast, or drawer rarely stays where it started — prop-drilled `isOpen` and threaded callbacks don't scale. Layers moves that into a **headless client**: declare once, mount an outlet, `await confirm.open(...)` from any call site (wired handle or bag-form). Zero-dep core; thin adapters bind each framework's reactivity. ## Engine model @@ -16,7 +16,7 @@ LayerClient ──┬── LayerStack (named, ordered) ── Layer[] 1. **Declare** — `layerOptions({ stack, key, ... })` brands the key with a `DataTag` so `open()` infers the response type. 2. **Observe** — subscribe to a stack snapshot; adapters bind `LayerStack.subscribe` / `getSnapshot` to UI reactivity. -3. **Call** — `await client.open({ ...options, payload })` or a wired handle's `open(payload)`; resolution happens when something calls `call.end(response)` or `call.dismiss(response)`. +3. **Call** — a wired handle's `open(payload)` (or bag-form `client.open({ ...options, payload })`); resolution happens when something calls `call.end(response)` or `call.dismiss(response)`. ## Package boundary diff --git a/apps/docs/content/concepts/when-to-use.mdx b/apps/docs/content/concepts/when-to-use.mdx index 9806070..d17cffc 100644 --- a/apps/docs/content/concepts/when-to-use.mdx +++ b/apps/docs/content/concepts/when-to-use.mdx @@ -7,7 +7,7 @@ search: Overlay UI is cross-cutting, but component models push its state local. You end up prop-drilling `isOpen`, lifting state, threading `onConfirm`/`onCancel`, duplicating modal boilerplate, and hand-rolling z-order and one-at-a-time logic. Layers moves overlay state into a headless `LayerClient` you invoke imperatively: declare a layer once, open it from anywhere. -Awaiting a result is **optional** — `void client.open({ ...toast, payload })` is a complete fire-and-forget call; the typed response is a bonus axis, not a requirement. +Awaiting a result is **optional** — `void toast.open(payload)` on a wired handle is a complete fire-and-forget call; the typed response is a bonus axis, not a requirement. | Use case | What it involves | Fit | | --------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | diff --git a/apps/docs/content/guides/awaiting-results.mdx b/apps/docs/content/guides/awaiting-results.mdx index 680d224..9a0842e 100644 --- a/apps/docs/content/guides/awaiting-results.mdx +++ b/apps/docs/content/guides/awaiting-results.mdx @@ -1,32 +1,35 @@ --- title: Awaiting results -description: Await typed responses from client.open, infer types via DataTag, or fire-and-forget with void. +description: Await typed responses from a wired handle (or bag-form open), infer types via DataTag, or fire-and-forget with void. search: tags: ["guide"] --- -Need a boolean back from a confirm? `await client.open(...)` returns `Promise`, and the response type flows from your layer definition with no explicit generic. Awaiting is optional — fire-and-forget is equally first-class. +Need a boolean back from a confirm? Wire the declaration, then `await confirm.open(...)` — `Promise` with no call-site generic. Awaiting is optional; fire-and-forget is equally first-class. ## Typed await -Brand the contract once with `layerOptions()`. The `key` carries a `DataTag`, so `await client.open({ ...confirm, payload })` infers `R`: +Brand the contract once with `layerOptions()`. The `key` carries a `DataTag`, so payload-only `open` infers `R`: ```tsx lineNumbers +import { createLayer } from "@stainless-code/layers"; + const confirm = layerOptions({ stack: "confirm", key: ["confirm", "remove"], component: ConfirmDialog, }); -const ok = await client.open({ ...confirm, payload: { title: "Remove?" } }); +const c = createLayer(confirm, client); +const ok = await c.open({ title: "Remove?" }); // ^? boolean ``` -You can also brand a key alone with `layerKey()(key)` when options are inlined at the call site. See [Identity and types](/concepts/identity-and-types) for how `DataTag` inference works. +Adapters bind the same handle with `useLayer` / `injectLayer` / `createLayer` (see tabs below). You can also brand a key alone with `layerKey()(key)` when options are inlined at the call site. See [Identity and types](/concepts/identity-and-types). ## Open from your framework -`client.open({ …layerOptions, payload })` works everywhere. Adapters also ship a **wired handle** — `useLayer(options)` (Svelte: `createLayer`; Angular: `injectLayer`) — for payload-only `open` plus reactive `state`/`queued`/`top`: +Adapters ship a **wired handle** — `useLayer(options)` (Svelte: `createLayer`; Angular: `injectLayer`) — for payload-only `open` plus reactive `state`/`queued`/`top`: @@ -184,18 +187,32 @@ async function remove() { +## Bag-form (escape hatch) + +`client.open({ …layerOptions, payload })` stays valid — headless call sites, dynamic keys, or when you already hold a `LayerClient`: + +```ts lineNumbers +const ok = await client.open({ ...confirm, payload: { title: "Remove?" } }); +// ^? boolean +``` + +See [Migration](/reference/migration) — bag-form remains first-class alongside Drive handles. + ## Fire-and-forget When a layer needs no return value, omit the response generic (defaults to `void`) and skip `await`: ```tsx +import { createLayer } from "@stainless-code/layers"; + const about = layerOptions({ stack: "modal", key: ["about"], component: About, }); -void client.open(about); +const c = createLayer(about, client); +void c.open(); ``` The layer dismisses itself — typically via `call.dismiss()` inside the component. Use `void` to signal intentional fire-and-forget when your linter flags floating promises. diff --git a/apps/docs/content/guides/error-handling.mdx b/apps/docs/content/guides/error-handling.mdx index 79aa66d..35de6a8 100644 --- a/apps/docs/content/guides/error-handling.mdx +++ b/apps/docs/content/guides/error-handling.mdx @@ -5,7 +5,7 @@ search: tags: ["guide"] --- -`client.open()` returns `Promise` — the direct-`await` contract. Resolution types flow through `DataTag`; **rejections do not** (a TypeScript limitation). Narrow errors in `catch` with runtime guards. +`open()` returns `Promise` — the direct-`await` contract. Resolution types flow through `DataTag`; **rejections do not** (a TypeScript limitation). Narrow errors in `catch` with runtime guards. ## What rejects @@ -17,8 +17,12 @@ The promise rejects when: Dismissal during `pending` resolves with the dismissal response (the in-flight `loadFn` is aborted via `AbortController`); it does not reject. ```ts lineNumbers +import { createLayer } from "@stainless-code/layers"; + +const c = createLayer(layer, client); + try { - const data = await client.open({ ...layer, payload }); + const data = await c.open(payload); } catch (err) { // [!code highlight] // err is unknown — narrow explicitly } @@ -27,10 +31,12 @@ try { ## Narrow validation errors ```ts lineNumbers -import { isPayloadValidationError } from "@stainless-code/layers"; +import { createLayer, isPayloadValidationError } from "@stainless-code/layers"; + +const c = createLayer(prompt, client); try { - await client.open({ ...prompt, payload: untrusted }); + await c.open(untrusted); } catch (err) { if (isPayloadValidationError(err)) { // [!code highlight] showFieldErrors(err.issues); @@ -59,6 +65,8 @@ declare module "@stainless-code/layers" { When a layer defines `loadFn`, a throw rejects the caller's `await`: ```ts lineNumbers +import { createLayer } from "@stainless-code/layers"; + const detail = layerOptions<{ id: string }, Detail, ApiError>({ stack: "modal", key: ["detail"], @@ -70,8 +78,10 @@ const detail = layerOptions<{ id: string }, Detail, ApiError>({ }, }); +const c = createLayer(detail, client); + try { - await client.open({ ...detail, payload: { id: "42" } }); + await c.open({ id: "42" }); } catch (err) { if (err instanceof ApiError) { // [!code highlight] /* … */ @@ -82,7 +92,7 @@ try { The loaded `data` is available on the layer state when `loadFn` succeeds; on error the layer enters `phase: "error"`. :::note -A `Result`-returning `open` was deliberately rejected to preserve direct `await`. Narrow in `catch` instead. +A `Result`-returning `open` was deliberately rejected to preserve direct `await`. Narrow in `catch` instead. Bag-form `client.open({ …opts, payload })` rejects the same way. ::: See [Payload validation](/guides/payload-validation) for `PayloadValidationError` and [Lifecycle](/concepts/lifecycle) for `phase: "error"`. diff --git a/apps/docs/content/guides/getting-started.mdx b/apps/docs/content/guides/getting-started.mdx index bf1565d..17c719b 100644 --- a/apps/docs/content/guides/getting-started.mdx +++ b/apps/docs/content/guides/getting-started.mdx @@ -79,24 +79,7 @@ function App() { ## Open and await -Call `client.open` from anywhere inside the provider — the response type is inferred: - -```tsx -import { useLayerClient } from "@stainless-code/react-layers"; - -function useRemove() { - const client = useLayerClient(); - return async () => { - const ok = await client.open({ ...confirm, payload: { title: "Remove?" } }); // [!code highlight] - // ^? boolean - if (ok) { - /* … */ - } - }; -} -``` - -Or wire the declaration with `useLayer(options)` — same inference, payload-only `open`, plus reactive `state`/`queued`/`top` for UI: +Wire the declaration with `useLayer(options)` — payload-only `open`, inferred `R`, plus reactive `state`/`queued`/`top`: ```tsx import { useLayer } from "@stainless-code/react-layers"; @@ -105,7 +88,7 @@ function RemoveButton() { const confirmLayer = useLayer(confirm); async function handleRemove() { - const ok = await confirmLayer.open({ title: "Remove?" }); + const ok = await confirmLayer.open({ title: "Remove?" }); // [!code highlight] // ^? boolean if (ok) { /* … */ @@ -120,6 +103,6 @@ function RemoveButton() { } ``` -See [Glossary](/concepts/glossary) for drive vs observe. +Bag-form stays valid when you already hold a client: `await client.open({ ...confirm, payload })`. See [Glossary](/concepts/glossary) for drive vs observe and [Migration](/reference/migration) for the escape hatch. > Optional payload and response — see [Awaiting results](/guides/awaiting-results#fire-and-forget). diff --git a/apps/docs/content/guides/payload-validation.mdx b/apps/docs/content/guides/payload-validation.mdx index 9b7e412..e2bbe84 100644 --- a/apps/docs/content/guides/payload-validation.mdx +++ b/apps/docs/content/guides/payload-validation.mdx @@ -5,13 +5,15 @@ search: tags: ["guide"] --- -A bad payload at `open` should reject before a component ever mounts. Pass `validate` on `layerOptions` or at `open` to parse untrusted input **synchronously**; failure rejects `open` with `PayloadValidationError` and mounts nothing. +A bad payload at `open` should reject before a component ever mounts. Pass `validate` on `layerOptions` or at bag-form `open` to parse untrusted input **synchronously**; failure rejects `open` with `PayloadValidationError` and mounts nothing. -## Sync validator function +## Wired handle (`ValidatedLayerHandle`) + +When `validate` is on `layerOptions`, `createLayer` / `useLayer` return a `ValidatedLayerHandle` — `open`/`upsert` take schema **input**; `current` and `update` use parsed **output**: -A sync `(input) => output` function — throw on invalid: +```tsx lineNumbers +import { useLayer } from "@stainless-code/react-layers"; -```ts lineNumbers const prompt = layerOptions<{ label: string }, string>({ stack: "prompt", key: ["prompt", "rename"], @@ -28,25 +30,39 @@ const prompt = layerOptions<{ label: string }, string>({ }, }); -// open's payload arg is the validator INPUT; the layer stores OUTPUT -await client.open({ ...prompt, payload: { label: " hello " } }); -// layer sees { label: "hello" } +function RenameButton() { + const c = useLayer(prompt); + // open's arg is validator INPUT; the layer stores OUTPUT + return ( + + ); +} +// component payload: { label: "hello" } ``` +Headless hosts use `createLayer(prompt, client)` with the same input/output split. + ## Standard Schema Any library implementing [Standard Schema v1](https://standardschema.dev) works — Zod 4, Valibot, ArkType, etc.: ```ts import * as z from "zod"; +import { createLayer } from "@stainless-code/layers"; const schema = z.object({ email: z.string().email() }); -await client.open({ - ...invite, - payload: { email: "user@example.com" }, - validate: schema, +const invite = layerOptions<{ email: string }, void>({ + stack: "modal", + key: ["invite"], + component: Invite, + validate: schema, // [!code highlight] }); + +const c = createLayer(invite, client); +await c.open({ email: "user@example.com" }); ``` Core ships the `StandardSchemaV1` type interface — no schema-library dependency. @@ -56,10 +72,12 @@ Core ships the `StandardSchemaV1` type interface — no schema-library dependenc When validation fails, `open` rejects with `PayloadValidationError`: ```ts -import { isPayloadValidationError } from "@stainless-code/layers"; +import { createLayer, isPayloadValidationError } from "@stainless-code/layers"; + +const c = createLayer(prompt, client); try { - await client.open({ ...prompt, payload: untrusted }); + await c.open(untrusted); } catch (err) { if (isPayloadValidationError(err)) { for (const issue of err.issues) { @@ -69,37 +87,18 @@ try { } ``` -## Wired handle (`ValidatedLayerHandle`) +## Bag-form (escape hatch) -When `validate` is on `layerOptions`, `createLayer` / `useLayer` return a `ValidatedLayerHandle` — `open`/`upsert` take schema **input**; `current` and `update` use parsed **output**: +Pass `validate` at the call site when the options bag does not carry it: -```tsx lineNumbers -import { useLayer } from "@stainless-code/react-layers"; - -const prompt = layerOptions({ - stack: "prompt", - key: ["prompt", "rename"], - component: Prompt, - validate: (input: unknown) => { - if (typeof input !== "string" || !input.trim()) throw new Error("Required"); - return input.trim(); - }, +```ts +await client.open({ + ...invite, + payload: { email: "user@example.com" }, + validate: schema, }); - -function RenameButton() { - const promptLayer = useLayer(prompt); - - return ( - - ); -} -// component payload: "hello" (output) ``` -Headless hosts use `createLayer(prompt, client)` with the same input/output split. - ## Rules - Validation runs **synchronously** at `open` — async schemas throw at open time (`Async payload validation is not supported`). diff --git a/apps/docs/content/guides/singletons.mdx b/apps/docs/content/guides/singletons.mdx index 938bea7..3a41445 100644 --- a/apps/docs/content/guides/singletons.mdx +++ b/apps/docs/content/guides/singletons.mdx @@ -12,6 +12,8 @@ Toasts, snackbars, and progress indicators share one surface: a single layer ins Pass `upsert: true` when opening (or set it on `layerOptions`) so a second open with the same key merges into the existing layer instead of stacking a duplicate: ```ts lineNumbers +import { createLayer } from "@stainless-code/layers"; + const toast = layerOptions<{ msg: string }>({ stack: "toast", key: ["toast"], @@ -19,10 +21,13 @@ const toast = layerOptions<{ msg: string }>({ upsert: true, // [!code highlight] }); -void client.open({ ...toast, payload: { msg: "Saved" } }); -void client.open({ ...toast, payload: { msg: "Synced" } }); // same instance, payload merged +const c = createLayer(toast, client); +void c.open({ msg: "Saved" }); +void c.open({ msg: "Synced" }); // same instance, payload merged ``` +Adapters: `useLayer(toast)` / `injectLayer` / `createLayer` — same `open` / `upsert` ops. Bag-form `client.open({ ...toast, payload })` still works when you need a dynamic key. + ## Live updates Inside the layer, call `call.update(patch)` to patch payload without re-opening: @@ -58,7 +63,7 @@ function Progress({ call, payload }: LayerComponentProps<{ pct: number }>) { | Pattern | API | | ------- | --- | -| Replace content on repeat open | `upsert: true` on `open` | +| Replace content on repeat open | `upsert: true` on `open` / `layerOptions` | | Patch while mounted | `call.update(patch)` or `stack.update(layer, patch)` | | Dismiss the singleton | `call.dismiss()` or `stack.dismiss(layer)` | diff --git a/apps/docs/pages/_home/Batteries.astro b/apps/docs/pages/_home/Batteries.astro index 4b3250d..cf25693 100644 --- a/apps/docs/pages/_home/Batteries.astro +++ b/apps/docs/pages/_home/Batteries.astro @@ -42,8 +42,8 @@ import { contentHref } from "blume/components/content/base-href.ts"; icon="tag" title="Await a result — no call-site generics" > - await client.open(...) infers the response type end-to-end — - no explicit generic on the call site. + {"await confirm.open({ title })"} on a wired handle —{" "} + R flows from the key brand. ({ ```svelte - ``` diff --git a/packages/vue/skills/vue-layers/SKILL.md b/packages/vue/skills/vue-layers/SKILL.md index 74c9ca7..9fa80c1 100644 --- a/packages/vue/skills/vue-layers/SKILL.md +++ b/packages/vue/skills/vue-layers/SKILL.md @@ -147,13 +147,13 @@ export const notice = layerOptions({ ```vue