Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
e001298
feat: add Lit adapter at full matrix parity
SutuSebastian Jul 17, 2026
108e9a7
harden: Lit adapter polish for ship readiness
SutuSebastian Jul 17, 2026
bf43e4a
harden: close Lit docs drift and validated-handle coverage
SutuSebastian Jul 17, 2026
ee9ed14
harden: bump Layers multi-framework score to seven in comparison
SutuSebastian Jul 17, 2026
c8f1e23
fix(lit): share one context resolve across useLayer stacks
SutuSebastian Jul 17, 2026
e85ca68
fix(lit): share context resolve for useLayerGroup outlet
SutuSebastian Jul 17, 2026
5430d5b
harden: include Lit in skill roster and maintainer docs
SutuSebastian Jul 17, 2026
c3ae5f3
harden: rebind Lit stacks on context client swap
SutuSebastian Jul 17, 2026
317722b
harden: pin AppStack client wiring and headless coverage
SutuSebastian Jul 17, 2026
4334ddc
harden: Lit no-remount coverage and guide tabs
SutuSebastian Jul 17, 2026
d5515f7
chore: retire Lit plan and add changeset
SutuSebastian Jul 17, 2026
b137c98
docs: Lit host-first signatures and voice polish
SutuSebastian Jul 17, 2026
e0867ef
docs(lit): document exported context token on the API
SutuSebastian Jul 17, 2026
7cf5c58
docs: align Lit surfaces with docs-voice order and disclaimer
SutuSebastian Jul 17, 2026
c3c870b
docs(lit): match adapter page opener to siblings
SutuSebastian Jul 17, 2026
7b131b5
docs(lit): fact-check recipes against Lit and adapter APIs
SutuSebastian Jul 17, 2026
9aae20e
chore(lit): raise size-limit to 4.5 KB for CE surface
SutuSebastian Jul 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/docs-voice/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ was rejected) and the "when **not** to use it" anti-sell — keep both.
`meta.ts` icons and tab icons stay. Nested brand groups (e.g. Svelte): icon
on the group only — don't repeat the same SVG on indented children.
- **Adapter list order:** core/vanilla → react → preact → solid → angular →
vue → svelte (runes → store). Every multi-adapter listing.
vue → lit → svelte (runes → store). Every multi-adapter listing.

## Don't

Expand Down
3 changes: 3 additions & 0 deletions .agents/skills/harden-pr/LEDGER.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ By-design or false-positive findings — do not re-raise.
- **[correctness]** `packages/core/src/layerStack.ts` — snapshot cached + rebuilt inside `notifyManager.batch`: by-design — `useSyncExternalStore` needs a referentially-stable snapshot between mutations or it infinite-loops.
-->

- **[public-api]** `packages/lit/src/index.ts` `layerClientContext` export — by-design; rationale on the const JSDoc.
- **[public-api]** `packages/lit/src/index.ts` `StackController` `queued`/`deferClient`/`bindClient` — by-design; rationale on the class/ctor JSDoc (factories are the supported path).

## Deferred

Capped or out-of-scope-for-now — reconcile re-vets; remove lines when fixed.
Expand Down
4 changes: 2 additions & 2 deletions .agents/skills/improve-codebase-architecture/LANGUAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ _Examples in this repo_: the package seam in `docs/architecture.md` — **core v

**Adapter**
A concrete thing that satisfies an interface at a seam. Describes _role_ (what slot it fills), not substance (what's inside).
_Examples in this repo_: each framework binding (`react`, `svelte`, `vue`, `solid`, `preact`, `angular`) is an adapter at the core/adapter seam. Six adapters across the seam = a real seam (the engine is genuinely framework-agnostic); a single adapter would be hypothetical.
_Examples in this repo_: each framework binding (`react`, `preact`, `solid`, `angular`, `vue`, `lit`, `svelte`) is an adapter at the core/adapter seam. Seven adapters across the seam = a real seam (the engine is genuinely framework-agnostic); a single adapter would be hypothetical.

**Leverage**
What callers get from depth. More capability per unit of interface they have to learn. One implementation pays back across N call sites and M tests.
Expand All @@ -51,7 +51,7 @@ _Example_: the `notifyManager.batch` wrapping lives in one place in `packages/co

- **The interface is the test surface.** Callers and tests cross the same seam. If you want to test _past_ the interface, the module is probably the wrong shape. Example: the zero-dep core invariant (`packages/core` has no dependencies and imports no framework peer) belongs at the package seam, enforced by package manifests plus dependency/import tooling rather than tests that mock internals.

- **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a seam unless something actually varies across it. Example: a core/adapter seam with only a React adapter is hypothetical; add Svelte, Vue, Solid, Preact, and Angular and it's a real seam (each framework's reactivity is genuinely different). The seam is justified because the engine is the same across all six — don't introduce a per-adapter core fork before a second adapter genuinely needs it.
- **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a seam unless something actually varies across it. Example: a core/adapter seam with only a React adapter is hypothetical; add Preact, Solid, Angular, Vue, Lit, and Svelte and it's a real seam (each framework's reactivity is genuinely different). The seam is justified because the engine is the same across all seven — don't introduce a per-adapter core fork before a second adapter genuinely needs it.

## Relationships

Expand Down
5 changes: 5 additions & 0 deletions .changeset/lit-adapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@stainless-code/lit-layers": minor
---

Add `@stainless-code/lit-layers` — full-matrix Lit adapter (Reactive Controllers, `@lit/context`, light-DOM stack CEs via `defineStackElements()`).
2 changes: 1 addition & 1 deletion .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
`@stainless-code/layers` is a small, freshly extracted library. Before large PRs, please open an issue so we can align on:

- **Public surface** — anything exported from a package entry point (`src/index.ts` / React's `src/index.tsx`, plus Svelte's `src/store.ts`) is the public API and must carry JSDoc that reads well in hovers and published typings. See [`docs/architecture.md`](../docs/architecture.md) for the core + adapter model.
- **Runtimes** — **Node** `^20.19.0 || >=22.12.0` and **Bun** `>=1.0.0` (`package.json` **engines**). The core is zero-dep by design; each adapter package declares its required peer (`react`, `preact`, `svelte`, `vue`, `solid-js`, `@angular/core`).
- **Runtimes** — **Node** `^20.19.0 || >=22.12.0` and **Bun** `>=1.0.0` (`package.json` **engines**). The core is zero-dep by design; each adapter package declares its required peer (`react`, `preact`, `solid-js`, `@angular/core`, `vue`, `lit` + `@lit/context`, `svelte`).

## Dev workflow

Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/1_bug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ body:
- Node.js
- Bun
- Browser (React)
- Browser (Svelte / Vue / Solid / Preact / Angular)
- Browser (Svelte / Vue / Solid / Preact / Angular / Lit)
- Both / not sure
validations:
required: true
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/2_feature_adapter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ body:
- type: markdown
attributes:
value: |
**Adapters** plug in via one seam — a binding that subscribes to `LayerStack.subscribe`/`getSnapshot` and renders the active stack in that library or framework's reactivity (`useSyncExternalStore`, Svelte runes, Vue refs, Solid accessors, Angular signals, Preact compat). See [`docs/architecture.md`](https://github.com/stainless-code/layers/blob/main/docs/architecture.md). Open an issue here first so we can agree on shape (entry point, optional peer, API) before a large PR.
**Adapters** plug in via one seam — a binding that subscribes to `LayerStack.subscribe`/`getSnapshot` and renders the active stack in that library or framework's reactivity (`useSyncExternalStore`, Svelte runes, Vue refs, Solid accessors, Angular signals, Preact compat, Lit Reactive Controllers). See [`docs/architecture.md`](https://github.com/stainless-code/layers/blob/main/docs/architecture.md). Open an issue here first so we can agree on shape (entry point, optional peer, API) before a large PR.

- type: textarea
id: problem
Expand Down
7 changes: 7 additions & 0 deletions .size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@
"limit": "3.5 KB",
"gzip": true
},
{
"name": "@stainless-code/lit-layers",
"path": "packages/lit/dist/index.mjs",
"ignore": ["@stainless-code/layers", "lit", "@lit/context"],
"limit": "4.5 KB",
"gzip": true
},
{
"name": "@stainless-code/solid-layers",
"path": "packages/solid/dist/index.mjs",
Expand Down
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,24 @@

Modals are just async functions you forgot to `await`.

Headless layer/stack manager — modals, dialogs, drawers, popovers, toasts are **layers in named stacks**. Open any layer from anywhere and `await` a typed result. A zero-dependency core plus React, Preact, Solid, Angular, Vue, and Svelte adapters. State coordination, not UI ownership: Layers owns ordering, keys, transitions, blockers, and the `await client.open(...)` contract; you own rendering, focus, portals, and a11y.
Headless layer/stack manager — modals, dialogs, drawers, popovers, toasts are **layers in named stacks**. Open any layer from anywhere and `await` a typed result. A zero-dependency core plus React, Preact, Solid, Angular, Vue, Lit, and Svelte adapters. State coordination, not UI ownership: Layers owns ordering, keys, transitions, blockers, and the `await client.open(...)` contract; you own rendering, focus, portals, and a11y.

> Experimental — the API may change between minor releases. Pin your version. ([Stability & versioning](https://stainless-code.com/layers/concepts/stability))

Full docs: [stainless-code.com/layers](https://stainless-code.com/layers).

## Packages

| Package | Peer | Install |
| ---------------------------------------------------- | --------------- | ---------------------------------------- |
| [`@stainless-code/layers`](packages/core) | — | `bun add @stainless-code/layers` |
| [`@stainless-code/react-layers`](packages/react) | `react` | `bun add @stainless-code/react-layers` |
| [`@stainless-code/preact-layers`](packages/preact) | `preact` | `bun add @stainless-code/preact-layers` |
| [`@stainless-code/solid-layers`](packages/solid) | `solid-js` | `bun add @stainless-code/solid-layers` |
| [`@stainless-code/angular-layers`](packages/angular) | `@angular/core` | `bun add @stainless-code/angular-layers` |
| [`@stainless-code/vue-layers`](packages/vue) | `vue` | `bun add @stainless-code/vue-layers` |
| [`@stainless-code/svelte-layers`](packages/svelte) | `svelte` | `bun add @stainless-code/svelte-layers` |
| Package | Peer | Install |
| ---------------------------------------------------- | --------------------- | ---------------------------------------- |
| [`@stainless-code/layers`](packages/core) | — | `bun add @stainless-code/layers` |
| [`@stainless-code/react-layers`](packages/react) | `react` | `bun add @stainless-code/react-layers` |
| [`@stainless-code/preact-layers`](packages/preact) | `preact` | `bun add @stainless-code/preact-layers` |
| [`@stainless-code/solid-layers`](packages/solid) | `solid-js` | `bun add @stainless-code/solid-layers` |
| [`@stainless-code/angular-layers`](packages/angular) | `@angular/core` | `bun add @stainless-code/angular-layers` |
| [`@stainless-code/vue-layers`](packages/vue) | `vue` | `bun add @stainless-code/vue-layers` |
| [`@stainless-code/lit-layers`](packages/lit) | `lit`, `@lit/context` | `bun add @stainless-code/lit-layers` |
| [`@stainless-code/svelte-layers`](packages/svelte) | `svelte` | `bun add @stainless-code/svelte-layers` |

Install only the adapter for your framework — it pulls the core in transitively and re-exports it, so adapter APIs (`StackProvider`, `useStack`, …) and core APIs (`LayerClient`, `layerOptions`, …) both come from the one package. Each adapter lists its framework as a required peer. Svelte ships two entries: `@stainless-code/svelte-layers` (runes, 5.7+) and `@stainless-code/svelte-layers/store` (stores, 3+).

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/blume.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { defineConfig } from "blume";
export default defineConfig({
title: "Layers",
description:
"Headless modal/dialog/drawer/popover/toast manager — open any layer from anywhere. Zero-dep core + React, Preact, Solid, Angular, Vue, and Svelte adapters.",
"Headless modal/dialog/drawer/popover/toast manager — open any layer from anywhere. Zero-dep core + React, Preact, Solid, Angular, Vue, Lit, and Svelte adapters.",

logo: { image: "/logo.svg", text: "Layers" },

Expand Down
4 changes: 3 additions & 1 deletion apps/docs/components/blume/Footer.astro
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const groups: { label: string; links: { href: string; label: string; external?:
{ href: contentHref("/adapters/solid"), label: "Solid" },
{ href: contentHref("/adapters/angular"), label: "Angular" },
{ href: contentHref("/adapters/vue"), label: "Vue" },
{ href: contentHref("/adapters/lit"), label: "Lit" },
{ href: contentHref("/adapters/svelte/runes"), label: "Svelte" },
],
},
Expand Down Expand Up @@ -153,7 +154,8 @@ const groups: { label: string; links: { href: string; label: string; external?:
class="inline-block size-1.5 rounded-full bg-accent"
aria-hidden="true"
/>
Experimental — the API may change between minor releases.
Experimental — the API may change between minor releases. Pin your
version.
</p>
</div>
</div>
Expand Down
31 changes: 19 additions & 12 deletions apps/docs/content/adapters/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@ Pick a framework binding — same engine, native reactivity. The [core](/adapter

## Adapter ergonomics

Same primitives on every adapter: client context, `useStack`, **drive** handles, **observe** hooks. Engine parity; wrappers (`StackOutlet`, `createStackHook`, …) are sugar and diverge by design. React/Preact/Solid/Angular/Vue re-export the full core; Svelte re-exports selectively. Angular and Svelte ship compiler-free — imperative / primitive render, no shipped host components.
Same primitives on every adapter: client context, `useStack`, **drive** handles, **observe** hooks. Engine parity; wrappers (`StackOutlet`, `createStackHook`, …) are sugar and diverge by design. React/Preact/Solid/Angular/Vue/Lit re-export the full core; Svelte re-exports selectively. Angular and Svelte ship compiler-free — imperative / primitive render, no shipped host components. Lit: `<stack-provider>` is shadow + `<slot>`; outlet / subscribe / `app-host` are light DOM.

| Capability | React | Preact | Solid | Angular | Vue | Svelte (runes) | Svelte (store) |
| ------------------------------------------------------ | :---: | :----: | :---: | :-----: | :-: | :------------: | :------------: |
| Primitives + core re-export | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Drive: `useLayer` / `injectLayer` / `createLayer` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Observe: `useLayerState` / `injectLayerState` / `createLayerState` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `StackOutlet` (built-in host) | ✅ | ✅ | ✅ | ✅¹ | ✅ | —⁴ | —⁴ |
| `StackSubscribe` (render-prop / scoped slot) | ✅ | ✅ | ✅ | —² | ✅ | —⁴ | —⁴ |
| `useStackHandles` (headless `{ states, getCall }`) | ✅ | ✅ | ✅ | ✅ | ✅ | —⁵ | —⁵ |
| `useMutationFlow` (async-action helper) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `useLayerGroup` (nested-stack hook) | ✅ | ✅ | ✅ | ✅³ | ✅ | ✅⁶ | ✅⁶ |
| `createStackHook` (`useAppStack`/`AppHost`/`AppLayer`) | ✅ | ✅ | ✅ | ✅³ | ✅ | —⁴ | —⁴ |
| Capability | React | Preact | Solid | Angular | Vue | Lit | Svelte (runes) | Svelte (store) |
| ------------------------------------------------------ | :---: | :----: | :---: | :-----: | :-: | :-: | :------------: | :------------: |
| Primitives + core re-export | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Drive: `useLayer` / `injectLayer` / `createLayer` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Observe: `useLayerState` / `injectLayerState` / `createLayerState` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `StackOutlet` (built-in host) | ✅ | ✅ | ✅ | ✅¹ | ✅ | ✅ | —⁴ | —⁴ |
| `StackSubscribe` (render-prop / scoped slot) | ✅ | ✅ | ✅ | —² | ✅ | ✅ | —⁴ | —⁴ |
| `useStackHandles` (headless `{ states, getCall }`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | —⁵ | —⁵ |
| `useMutationFlow` (async-action helper) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `useLayerGroup` (nested-stack hook) | ✅ | ✅ | ✅ | ✅³ | ✅ | ✅ | ✅⁶ | ✅⁶ |
| `createStackHook` (`useAppStack`/`AppHost`/`AppLayer`) | ✅ | ✅ | ✅ | ✅³ | ✅ | ✅ | —⁴ | —⁴ |

¹ Angular ships `renderStack(vcr)` — a compiler-free imperative outlet (`ViewContainerRef.createComponent` + `setInput`, id-keyed so state changes update inputs without recreating components). ² Angular has no `StackSubscribe`; `useStack({ stack, select })` as a signal is the idiomatic equivalent. ³ Angular's `useLayerGroup`/`createStackHook` expose `renderInto(vcr)` instead of an `Outlet` component; `createStackHook` returns `{ provideClient, useAppStack, renderInto }` (no `AppHost`/`AppLayer`). ⁴ Svelte ships no `.svelte` components: render with `{#each useStack({ stack }).current as s}` + `callFor(s)` (runes) / `{#each $stack}` + `callFor(client, stackId, s)` (store) in your own markup. ⁵ Svelte's `useStack({ stack })` already returns the headless pair — `.current` (states) + `callFor`. ⁶ Svelte's `useLayerGroup` exposes the child `stack` (`SvelteStack` / `Readable`) instead of an `Outlet`.

Expand Down Expand Up @@ -75,6 +75,13 @@ Name map: Angular `injectLayer` (`useLayer` alias); Svelte `createLayer` / `crea
description="@stainless-code/vue-layers"
arrow
/>
<Card
title="Lit"
icon="/icons/lit.svg"
href="/adapters/lit"
description="@stainless-code/lit-layers"
arrow
/>
<Card
title="Svelte (runes)"
icon="/icons/svelte.svg"
Expand Down
Loading
Loading