Repository navigation
release: v4.4.0 — client-side rendering - #78
Merged
Merged
Conversation
Routes cover png/svg/takumi x default/cog/prerendered, built with @ethercorps/svelte-adapter-universal for node, bun, deno and workers.
ultrahtml rebuilds its pseudo-class regex by string-matching "(?<argument>¶*)" inside another regex's .source. Minifiers that escape non-ASCII (e.g. terser ascii_only emits \xb6) break the match, so :not() and :nth-child() throw. 1.7.0 still has the bug. Match the argument group regardless of escaping: /\(\?<argument>[^)]*\)/. Replaces the 1.6.0 patch, which only covered ¶ and \u00B6.
dev + -next.x -> next, main + clean version -> latest. -next.x no longer publishes from main, and the beta channel is removed.
New `@ethercorps/sveltekit-og/client` export for generating OG images
in the browser or a worker, no server request. Single surface:
`ImageResponse` (extends Response) + `createImage`, with the engine
chosen in config: `{ engine: 'takumi' | 'satori', ...opts }` (default
takumi), typed as a per-engine discriminated union.
Fully self-contained under src/lib/client/ — own browser wasm providers
(satori/yoga + resvg via bundler `?url` + fetch(new URL), same-origin so
no CORS, worker-safe) and own satori/resvg render path that reuses only
the engine-agnostic shared helpers. Server render code stays untouched.
- Svelte components rendered client-side via mount into a detached
shadow root (not svelte/server), isolating page CSS from the image
and the component's styles from the page.
- Playground demo route at /client (ssr=false).
- Free resvg wasm objects after render; clamp satori raster formats to
png so Content-Type matches the bytes; revoke object URLs only once
the replacement is ready.
…yBuffer()/text() in browsers Browsers reject Response.arrayBuffer()/blob()/text() with a bare "TypeError: Failed to fetch" when the body stream errors, dropping the ImageResponseError and its code. Read the stream directly instead, and unwrap the shared builder's UNKNOWN_ERROR wrapper so callers see the inner code (COMPONENT_IN_WORKER, FONT_LOAD_FAILED, ...).
…e-split chunks) Vite's default iife worker format can't code-split, and the client entry lazy-imports its engines, so `vite build` failed for any worker using it. The demo worker also stops importing a .svelte file (the worker sub-build has no Svelte plugin); a non-string stand-in exercises the same guard.
…ts, fix docs Browsers can't set the User-Agent, so Google Fonts serves woff2 and loadGoogleFont rejects it. Satori needs resolved font data, so the docs example now goes through resolveFonts with CustomFont. Docs also state that takumi-js is required with either engine (the takumi chunk is resolved at build time) and that workers need worker.format "es".
sveltekitOG() added unwasm to every build. On the client build it rewrote the /client entry's takumi wasm into something WebAssembly.instantiate can't load. Vite computes env.isSsrBuild before SvelteKit sets build.ssr, so check the merged config and enforce: post to run after sveltekit().
Shared ClientDemo component (@examples/shared/client) renders one PNG in the browser with the default engine; satori-only passes engine="satori". Index pages gain a Client-side section. Examples now use sveltekitOG() instead of a raw rollupWasm in build.rollupOptions so the plugin can stay off the client build. satori-only gains takumi-js: the client entry resolves the takumi chunk at build time regardless of engine.
…/client page layout New shared ClientImage component renders the PNG on mount; the index card shows it as a real thumbnail (same height as the other cards — the old 'open page' placeholder broke the aspect ratio) and /client reuses it. The /client page's dark background moves to body so no white gutters appear, and its header matches the index hero.
Index pages show two live client cards (Takumi default, Satori · resvg); /client picks the engine from ?engine=satori and offers a Takumi/Satori switch. satori-only and takumi-only keep their single card.
feat(client): browser/worker rendering via /client entry
✅ Deploy Preview for sveltekit-og canceled.
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
sveltekit-og | 79bae83 | Oct 13 2026, 09:03 AM |
✅ Deploy Preview for sveltekit-og-dev canceled.
|
…yout Preview pane gets an engine / browser / both toggle. The browser view lays the same markup out in a sandboxed iframe (or mounts Card.svelte) at the OG size and scales it to fit; frames size from the pane height so two stack without cropping.
… side paneforge splits editor | preview horizontally and the preview into engine / browser shells vertically; every divider drags (keyboard too), layout persists via autoSaveId. Each render gets its own header with its own facts. Below lg the shells become Edit / engine / Browser / Code tabs.
satori-html emits children: [] for every empty element and satori treats any array of children on a non-flex <div> as more than one child, so an empty decorative <div> (a dot, a rule) always failed with SATORI_RENDER_FAILED. Drop empty children arrays in createVNode; covers the server and client paths.
…template - Format pretty-prints the markup: one element per line, long style attributes split into one declaration per line (the engines strip newlines and trim, so the render is unchanged). - Tailwind template using the tw attribute, rendered by both engines; the browser shell maps tw to class and loads the Tailwind browser build for it (sandbox allow-scripts, opaque origin). - Docs template's dot <div> gets display:flex so it renders on Satori with the published package too (the real fix is in createVNode).
Each HTML template now ships three variants: satori (flex only, the subset both engines share), takumi (CSS grid, box-shadow, gradients — Satori rejects grid, and the playground says so), tailwind (tw attribute, both engines). A Variant control sits next to the template; picking takumi follows with the engine. The standalone Tailwind template folds into the variants.
…ngine The three-way satori / takumi / tailwind variant read like a second engine picker. Now the toolbar has Engine and Styling; the vanilla template is chosen per engine (grid on Takumi, flex on Satori) and Tailwind is shared. Switching engine reloads the template unless the markup was edited.
…footer 100dvh minus the sticky header and the footer, and no min-height so short viewports fit too.
Engines and formats show as Takumi / Satori / PNG / JPEG / WebP / SVG (the snippet keeps the API values). Below lg the toolbar is Template · Engine · Options; styling, format and size live in an Options popover, Download moves into the render head, Copy into the Code tab, Reset into the editor head. The workbench is one screen tall on every size, so panes scroll and the page never does.
The drawer slides open/closed (svelte slide, 180 ms, cubicOut) with the caret rotating; template and options popovers fade and drop in and out via @starting-style + allow-discrete. Both collapse to instant under prefers-reduced-motion.
The passive '⌘↩ renders now' hint becomes a Render button (fires the immediate render; keycap shows the shortcut on lg) next to Format, both with icons in the small bordered head-button style shared by Reset, Copy and Download. Icon-only below sm so the head never clips.
dirty compared raw text, so Format → Satori kept the formatted Takumi grid markup and Satori rejected it. Compare formatted (the formatter is idempotent); engine, style and template switches carry the pretty-printed state over.
The Takumi grid version (title, author row, series card) is now also the flex version for Satori and the tw version; the Tailwind card is a solid rose since Satori's tw has no gradients.
…styling The Takumi grid designs (tiles, shadows, gradients) are rebuilt with flex for Satori and as tw for Tailwind, so switching engine or styling changes the markup, not the picture. Tailwind uses solid fills where Satori's tw has no gradients.
Pages' v2 build reads .node-version from the project root directory (apps/docs), not the repository root, so it kept picking 22.16 and failed SvelteKit 3's engines check (>=22.17).
Pages' v2 build ignored the major-only '24' in .node-version (both at the repo root and in apps/docs) and kept its 22.16 default; its docs only show a full version, so give it one.
The 'sveltekit-og' Pages project builds from examples/cf-pages-build and only reads .node-version there; the workspace install resolves the Kit 3 examples too, so it needs >=22.17 even though the example itself is Kit 2.
client.md and the playground pointed at @next; takumi.md still told people to install 4.3 from a beta tag.
This branch was successfully deployed
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.
Summary
Promotes
devtomainfor the 4.4.0 release.devis bumped to a clean4.4.0; merging publishes it tolatest(OIDC). Pre-releases shipped as4.4.0-next.0…next.2.Added
@ethercorps/sveltekit-og/client(feat(client): browser/worker rendering via /client entry #74): render OG images in the browser or a web worker with Takumi (default) or Satori + ReSVG — no server request.import(); only the one you pick is downloaded.COMPONENT_IN_WORKER, HTML strings work everywhere..blob()/.arrayBuffer()/.text()asImageResponseErrorwith acode.takumi-jswith either engine; workers needworker: { format: "es" }.sveltekitOG()plugin uses the Environment API when present. Satori, Takumi and Bun examples run on Kit 3.@sveltejs/adapter-bunexample (examples/bun-build, deployed on Render) and a Bun runtime guide./clientpage for both engines./docs/playground): editor | Takumi/Satori render ÷ browser layout, resizable panes, Vanilla CSS / Tailwind variants of every template, formatter.Fixed
<div></div>dots, dividers) made Satori throw the multiple-children error — satori-html emitschildren: []; dropped before rendering, server and client.css="injected"styles (e.g. a multi-linelinear-gradient) crashed satori 0.25; whitespace-normalized like HTML strings.sveltekitOG()applies the wasm loader to the SSR build only (it broke the/cliententry's Takumi wasm on the client build).ultrahtml1.7.0 with a repo patch for the pseudo-selector regex (breaks under ascii-escaping minifiers).Chores
dev+-next.x→next;main+ clean version →latest; beta dropped.srvx-buildexample removed; Node 24 pinned for hosted builds.Verification
pnpm test: 37 passed, 1 skipped.svelte-package+ publint clean./client; vercel/netlify/cloudflare builds green on Node 24.4.4.0-next.2published and in use by the docs site.After merging
apps/docsto^4.4.0and drop the@nextinstall hints (docs page + playground).🤖 Generated with Claude Code