Conversation
Migrate the library, website and test workspaces from Qwik 1.x to Qwik 2.0.0-beta.37. - Rename packages: @builder.io/qwik -> @qwik.dev/core, @builder.io/qwik-city -> @qwik.dev/router - API renames: qwikCity() -> qwikRouter(), createQwikCity() -> createQwikRouter(), <QwikCityProvider> -> <QwikRouterProvider>, @qwik-city-plan -> @qwik-router-config - Drop the qwikRouterConfig option from createQwikRouter (resolved internally in v2) - Remove src/routes/service-worker.ts (v2 uses modulepreload; keep <ServiceWorkerRegister /> to unregister stale SWs) - tsconfig: jsxImportSource @qwik.dev/core + moduleResolution bundler - Bump Vite to ^7.3.5, vite-tsconfig-paths ^6, eslint-plugin-qwik v2 - Fix v2 type strictness: isSignal() arg, timeout type, task cleanup - pnpm-workspace: allow esbuild/sharp build scripts
Add AGENTS.md as the single source of truth describing the project, the v1->v2 rename table, v2 gotchas, monorepo layout, public API and commands. Add thin pointers for Claude, Gemini, Codex, Cursor and Copilot.
Upgrade Vite 7 → 8 across root, website, and test (within the Qwik v2 peer range vite >=6 <9; Vite 8 uses Rolldown). Build process overhaul (vite.config.ts): - Fix externalization. Root cause: package.json declared no dependencies / peerDependencies, so the config's dep-based externalization was a no-op and DOMPurify got inlined (~80 kB). Now @qwik.dev/core is a peerDependency and dompurify a dependency, so neither is bundled. - Single-pass multi-entry lib build (was two vite builds toggled by an ENTRY env var). Shared headless core is hoisted into one chunk instead of being duplicated across the index and headless entries. - ESM-only output (dropped CJS). exports now lists `types` first; added `module` and `sideEffects: false`. prepublishOnly runs build.types + build.lib. Result: index.qwik.mjs 99.6 kB → 17 kB. Dropped @types/dompurify (deprecated; DOMPurify 3.x ships its own types).
Drop the library's only runtime dependency. title/description are now rendered as text/JSX nodes (the sonner model) instead of being parsed as sanitized HTML via dangerouslySetInnerHTML + DOMPurify. The package is now dependency-free and fully tree-shakeable. BREAKING CHANGE: string title/description values that previously rendered as HTML markup now render as plain text. Pass JSX for rich content.
Refine the sonner port on Qwik v2: export useSonner and additional public types (ToastClassnames, ToastToDismiss, Action, Promise result types with JSX support), rework headless state/toast/toast-wrapper logic, icons, and styled CSS. Also wire tailwind + qwikRouter into the lib vite config, set rootDir in tsconfig.lib.json, drop the unused dev entry, and remove dead imports in root.tsx.
Broaden the Playwright e2e coverage and the test app routes/entries, run the suite against chromium + webkit, and refresh the website demo layout. Drop the unused test dev entry and the website postcss config (tailwind v4 needs none).
- Adopt a pnpm catalog for shared dependency versions across all workspaces. - Replace ESLint + Prettier with oxlint + oxfmt. eslint-plugin-qwik is loaded via oxlint's jsPlugins (.oxlintrc.json); the two type-aware Qwik rules (valid-lexical-scope, use-async-top) can't run under the JS-plugin runtime and are omitted. Remove all eslint/prettier configs and their tsconfig references, and point the VS Code extension recommendation at oxc. - Pin vite to ^7 in the catalog. Vite 8 switches to Rolldown, under which the Qwik optimizer fails to register internal runtime QRL symbols (_run/_task) in production builds, throwing Q14 (qrlMissingChunk) during prod SSR. Vite 7 (Rollup) builds clean; verified pnpm preview renders with 0 errors. - Track remaining follow-ups in todos.md.
Catalog/peer-dep alignment for @qwik.dev/core, tsconfig moduleResolution "bundler" + jsxImportSource, single-pass ESM lib build, and Playwright/Vite config for the v2 router. Vite pinned to ^7 (Rolldown breaks the prod optimizer).
AGENTS.md is now the single source of truth (CLAUDE.md/GEMINI.md point here); README/CONTRIBUTING updated for Qwik v2 + sonner 2.0.7 parity; todos.md tracks the migration. Removes stale .github/copilot-instructions.md.
Port the docs/demo app to @qwik.dev/router (createQwikRouter, QwikRouterProvider), refresh components and styles, and align the Toaster usage with the v2 DOM contract.
Test app migrated to @qwik.dev/router; basic.spec.ts covers promise/extended-promise/unwrap, focus return, toasterId routing, testId, and multi-direction swipe dismissal (36/36 chromium).
Migrate the library to @qwik.dev/core: multi-direction swipe with drag damping, per-toast heights/offsets filtered by position, measureHeight races fixed, sync$ pointer-capture, useSonner(), getToasts/getHistory, and sonner 2.0.7's verbatim styles.css + data-sonner-* DOM contract. BREAKING CHANGE: ESM-only, @qwik.dev/core peer dep, data-sonner-* attribute/class names.
On swipe-dismiss the toast snapped back to its initial position and faded in place instead of sliding off. Turning on swipeOut re-renders the <li>, and Qwik rewrites the style attribute from the style object — wiping the imperatively-set --swipe-amount-x/y. The swipe-out keyframes read those vars with no fallback, so transform computed to none. Commit the release offsets into the rendered style object so they survive the re-render.
Make the repo-wide scan explicit in the lint/fmt scripts (oxlint . / oxfmt .) so coverage of website/ and test/ can't silently regress, and update AGENTS.md which still documented the stale "oxlint src" scope.
The enter animation flips `data-mounted` false→true and lets CSS transition it, but a freshly-inserted element can't animate on its first paint. The flip was scheduled from a setup-time `requestAnimationFrame` (no `track`), so it raced Qwik's element commit — the very first toast (which also creates its `<ol>` in the same render) intermittently never animated. Anchor the flip to the element ref and defer it past the next paint with a double rAF (`afterNextPaint`), so the collapsed state is always painted before the flip. Verified deterministic on chromium + webkit. The dismiss-callback swipe test relied on the first toast snapping instantly into place, so it now waits for the enter animation to settle before measuring and swiping.
New opt-in `topLayer` prop promotes the toaster to the browser's top layer via the native Popover API (`popover="manual"` + `showPopover()`), so toasts render above native modals (`<dialog>.showModal()`) and fullscreen elements, which otherwise paint over any `z-index` (issue #11). Off by default for sonner parity; no-ops where the Popover API is unavailable. The styled entry ships the UA `[popover]` reset so the container spans the viewport as a transparent pass-through layer; the inner lists keep their fixed positioning and pointer events. Known limitation: on WebKit the promoted `<section>` is `display:none` until shown, so its `onQVisible$` (IntersectionObserver) subscription never fires and no toasts render while `topLayer` is on. Under investigation.
Adds the topLayer harness to the dev route (a native `<dialog>` modal with open/close + fire-toast-from-modal buttons and a `?topLayer=1` toggle) and an e2e spec asserting: default toasters stay behind a modal (issue #11), topLayer promotes them above it, the popover opens/closes with the toast count, and the full-viewport layer passes pointer events through. Popover-API assertions pass on Chromium; on WebKit they fail due to the subscription-timing limitation noted in the feat commit.
A `<button>` may only contain phrasing content, but the copy buttons wrapped their icon in a `<div>` (flow content). Qwik v2 rejects this during SSR (Code(Q12) SsrError: "HTML rules do not allow '<div>' at this location"), crashing `pnpm dev`. Swap the wrappers for `<span>` (valid in a button) in both Installation and CodeBlock, and rename the `.copy div` rule to `.copy span` so the flex centering is preserved.
These props now accept `Signal<T> | T` (joining `invert`), so they can change reactively at runtime on the SSR-resumed Toaster singleton instead of being read only at mount. Values are unwrapped with `isSignal()`: - position/theme: the reactive computed/task reads the unwrapped value, so a signal change re-runs on the resumed singleton. - dir: new `resolveDir()` helper unwraps the signal and re-reads the document direction each render, so `"auto"` keeps tracking the live `<html dir>`. - richColors: the per-toast computed unwraps the (now possibly-Signal) `defaultRichColors`. Adds an exported `Direction` type.
Qwik v2 SSR-entry migration for the demo/docs site: - entry.ssr.tsx now uses `createRenderer` from `@qwik.dev/router` instead of the manual `renderToStream` + `@qwik-client-manifest` wiring. - Remove the unused `entry.deno.ts` and `entry.dev.tsx` entry points. - Drop the unused `autoprefixer` dependency (and its lockfile entries).
- New `/docs` route: a full guide covering install, toast types, actions, promises, the Toaster props reference (including the signal-capable props), styling, top layer, and useSonner — with live interactive demos. - Link to the docs from the Hero. - Landing page: move the `<Toaster>` to the end of the tree and drop the stale resumability-positioning warning.
Stop tracking the local backlog file and add it to .gitignore; it stays on disk but is no longer part of the repo.
Replace test.yml with ci.yml: a reusable workflow (workflow_call) that runs the green bar (lint, build.types+build.lib, website/test typecheck) and Playwright across a chromium/webkit matrix with browser caching. Extract pnpm+Node+install into a composite action so jobs don't duplicate setup.
Release now reuses ci.yml as a quality gate before publishing. Switch the publish step to npm OIDC Trusted Publishing: pnpm pack rewrites catalog:/ workspace: specifiers into the tarball, then npm publish *.tgz --provenance authenticates via id-token (no NPM_TOKEN). pnpm publish is avoided because it does not yet support npm's OIDC trusted publishing.
Derive the npm dist-tag from the package version: prerelease versions (2.0.0-beta.N) publish under 'beta' so the 'latest' tag keeps pointing at the stable line; plain versions publish under 'latest'.
The lib build only bundles src/lib/** and never needed the router; qwikRouter() required a src/routes dir that git can't track when empty, so the lib build failed on a clean CI checkout. Remove the router dep, the qwikRouter plugin, and the unused root SSR harness (root.tsx, entry.ssr.tsx). Root dev/start now delegate to the test app (pnpm --filter test ...), and build is the lib build.
The Toaster wired its client-side toast subscription via
useOnDocument("DOMContentLoaded", ...). That one-shot event has already
fired by the time the qwikloader wires the listener in WebKit, so the
handler never ran and the singleton never subscribed: every toast was
published to zero subscribers and nothing rendered (all WebKit e2e tests
that open a toast failed).
Switch to the qinit Qwik init event, which the qwikloader dispatches on
readystatechange (and immediately on load) with a replay path for
late-registered listeners, so it fires regardless of page-load timing.
Unlike qvisible/useVisibleTask$ (IntersectionObserver-based) it also
fires when the Toaster renders below the fold.
The cache key omitted matrix.browser, so the chromium and webkit legs shared one entry. Only the first finisher saved it (one browser), and later runs got a cache hit that skipped the browser install — leaving the other leg launching against a missing binary (webkit-2311/pw_run.sh not found, all 42 webkit tests failing at launch). Add matrix.browser to the key so each browser gets its own entry, and bump a v2- prefix to discard the poisoned cache.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Warning
🚧 WIP — do not merge yet. This will stay a draft until Qwik v2 reaches a stable release. Until then it ships only as a prerelease (
qwik-sonner@beta).Summary
Migrates
qwik-sonnerfrom Qwik 1.x to Qwik 2.0 beta (@qwik.dev/core/@qwik.dev/router) and brings the toast core to sonner 2.0.7 parity. Published asv2.0.0-beta.1.Install the prerelease to test it today:
Full changelog: https://github.com/diecodev/qwik-sonner/releases/tag/v2.0.0-beta.1
Closes
topLayer(Popover API) support, toasts can now render above native modal dialogs.Highlights
@builder.io/*packages replaced with@qwik.dev/core/@qwik.dev/router;qwikCity()→qwikRouter(), service workers removed (v2 usesmodulepreload).dompurify(title/description render as text/JSX).topLayer(Popover API) support — opt-intopLayerprop renders toasts above native modal dialogs (closes the request in Use the popover api #11).Toasteraccepts Signals forposition,theme,richColors&dir.Status / open items
betaand cut a stablev2.0.0.Verification
pnpm build.types+pnpm build.libgreen at root;pnpm build.typesgreen inwebsite/andtest/; Playwright e2e (chromium + webkit) green in CI.