Skip to content

Releases: PuruVJ/ogygia

ogygia 0.8.5

Choose a tag to compare

@PuruVJ PuruVJ released this 22 Oct 10:50
69dcc7c

Changed

  • An island inside a live snippet an island renders follows the nested rule: it renders inline and hydrates with that island.
  • An island sets data-hydrated and fires ogygia:hydrated once the live snippets it renders have hydrated too.
  • A hole nested in an island renders inline as static HTML, like a lake: its component never ships to the browser, and an island inside it wakes on its own.

Fixed

  • A hole inside a live snippet an island renders stays deferred: it ships its fallback, loads on its schedule and is never bundled into the island (it never loaded before).
  • A live snippet that renders a template-root snippet, or reads a <script module> import or value, an exported declaration or a $: declaration, no longer throws ReferenceError at render.
  • A lake inside a live snippet an island renders keeps its frozen region, so the island inside it wakes and the lake's HTML is no longer discarded beside other snippet content.
  • A live snippet an island renders reads the page's contexts on the server and in the browser, as the island does.
  • An island inside a frozen snippet wakes on its own.
  • A snippet passed to an island on a Kit-hydrated page no longer causes a hydration mismatch.
  • A hole on a Kit-hydrated page keeps its address and its landed HTML through Kit's hydration (a hole inside an island could go empty).

ogygia 0.8.4

Choose a tag to compare

@PuruVJ PuruVJ released this 22 Oct 09:37
18eeaee

Fixed

  • ogygia always uses the app's SvelteKit: a second copy resolved from ogygia's own location (a linked ogygia whose peer resolved another Kit) left the devtools' server events, the freeze verdict and the page seed without their request.
  • A hole's answer morphed into a custom element with a shadow root keeps the answer's order: a reordered or newly shown child no longer stays in or lands at the wrong place.
  • A baked held region on a Kit-hydrated page no longer throws a hydration mismatch that emptied it: the region element is adopted as it stands, like a lake's.
  • Islands inside a baked held region on a Kit-hydrated page wake on the runtime instead of being left to Kit, which never hydrates them.
  • Custom elements inside a baked held region keep what the server's transform wrote (a closed dropdown no longer loses hidden and shifts the page).

ogygia 0.8.3

Choose a tag to compare

@PuruVJ PuruVJ released this 19 Oct 11:30
baef5e4

Added

  • ogygia/testing: record(actions, { blockNavigation: true }) holds back any document load the actions cause, keeps the page and lists each in navigations with what started it, seen as it began (the link, form or input being handled; approx where the browser cannot say).
  • ogygia/testing: compare() and toMarkdown() also report hydration per island instance, session counts (errors, shifts, slow interactions, blocked navigations, CLS) and vitals past a noise threshold (compare(base, head, { noise })), and take several runs a side ({ test, runs }): load signals by median, session signals only when every head run has them and no base run does.
  • ogygia/testing: each hydration() island has a key per instance (Name#n in page order, or the islandKey attribute on it or its nearest ancestor, across shadow roots) and its wake times (t0, done); session findings carry names; a session's counts.cls is a number, and toMeetBudget({ cls }) reads it.
  • ogygia/testing: playwrightDriver(page, { onNavigationHeld }) and turnOnDevtools(page, baseURL), the fixture's own pieces, for a test that holds its pages itself.
  • ogygia/testing: every report names the page's testing engine (engine.version, engine.lean); against a site on an older ogygia, record() fills in navigations from the loads it held, and one warning per site is logged for the whole run.
  • ogygia/testing: a changed island says whether only attributes differ (attributesOnly, with each in attributes) and whether input landed on it while it hydrated (inputDuringHydration); ignoreAttributes leaves chosen attribute changes out, and toHydrateCleanly({ allowAttributeChanges, allowInput }) lets them through.
  • ogygia/testing: page().self says what measuring cost the page (script_ms, the testing engine's bytes), and lean testing (lean: true, the og_testing=lean cookie) runs the testing API and nothing else of devtools, so the page loads what a visitor's does.
  • ogygia/testing: page().route (the SvelteKit route id), cpuFunctions for more than 15 of page().cpu.fns, and a session's failed requests and slowest interaction per element (by_element).
  • Profiler: record(…, { server: true }) works across the instances of a serverless deployment: the window's signed id rides each of the session's requests, every instance writes them to the profiler store, and stop on any instance gathers them (window.instances); a window that fails leaves the session whole with server: { error }.
  • Profiler: the JSON report names its engine.version; ?fields= (profile(path, { fields })) answers only the sections asked for; ?weigh=full weighs every file with no time budget, and each file it could not weigh says why (assets.missed_why).
  • A page with devtools switched on is sent Document-Policy: js-profiling, so the browser's CPU sampler can run on it.

Changed

  • OgygiaDriver.evaluate is loosely typed, so a driver's page.evaluate fits without a cast.
  • A pkg.pr.new preview is versioned <next patch>-next.<commit>, never the release it was built after.

Fixed

  • record() whose page left for another document now says so, with the actions' own error as cause, instead of "serve with devtools".
  • withOgygia turns devtools on for the first page a test opens when there is no baseURL; before, it was silently left off.
  • A page with devtools switched on is sent private, no-store with Vary: Cookie and is never frozen, so a shared cache cannot hand its events to other visitors; a profiler beacon page too.
  • A hole's Server-Timing reaches a browser that switched devtools on (it checked an old cookie value).
  • A profiler window whose stop never came frees its instance at its 60 s cap; before, that instance refused every later window.
  • A file the profiler failed to weigh by a timeout is weighed again on the next profile; before, it stayed a miss for the life of the instance.
  • Devtools' Page and Timeline tabs, and page(), stay on the page after a router navigation that never swapped it (handed to the browser and held back, or overtaken); before, they reported no islands.
  • On Windows, ogygia knows its own files again: builds no longer register its devtools and profiler components as app modules or warn that its own marks are ignored.
  • The dev dependency scan no longer fails when the profiler is on, so dependencies pre-bundle again; devalue is now named through ogygia, so pnpm apps stop warning that it can't be resolved.
  • A click on a load, idle, visible or media island before it is live is no longer lost: the island wakes at once and the click is replayed when it is live, as on an interaction island; hydratedBy() then says 'interaction'.
  • A Ctrl/Cmd/Shift- or middle-click on a link in an island that is not live yet opens the link as the browser would; before, it was canceled and replayed, and the replay could not open it.

0.8.2

Choose a tag to compare

@PuruVJ PuruVJ released this 14 Oct 12:41
c956ddf

Changed

  • In a build with devtools, the launcher stays hidden until a browser turns devtools on (?og-devtools=on, or ogygia.devtools.on() in the console); ?og-devtools=off, ogygia.devtools.off() or hide in the dock turn it off. The dev server is unchanged.

Fixed

  • A hole's CSS now carries components taken through a package (its exports, a TypeScript entry included) or a barrel — only the names imported — and an island inside a hole brings its own CSS, so what it renders after a click is styled.

0.8.1

Choose a tag to compare

@PuruVJ PuruVJ released this 13 Oct 09:33
39d3ec5

Fixed

  • handle({ endpoint }) now moves every hole and live-region URL a page mints, not only the handler; before, every hole asked the old /__ogygia__ path and stayed empty.

v0.8.0 — passage

Choose a tag to compare

@PuruVJ PuruVJ released this 09 Oct 14:07
a8284bf

The passage release: the runtime and the compiler rebuilt on one identity primitive, regions that move instead of reset across navigations, a standalone compiler, a production SSR profiler, a devtools dock, a typed router, frozen pages and fragment federation. Underneath, one load scheduler, one DOM ownership model and one page model replace per-path rules.

Breaking changes

  • Flags and experiments collapse onto one primitive, flag(); experiment, layer, allowOverrides, onExposure, batchExposures, .bucket, .on and routes({ experiments }) are removed.
    Declare with a shape (flag('x'), flag('x', 10), flag('x', fn), flag('x', { control: 80, bold: 20 })), read by calling it (checkout(c)), branch with flag.pick(map) / flag.pick(c, map), read payloads with flag.value(c), configure once with decide({ source, overrides, exposure, batch }), and pre-decide in a route-only shell with routes({ flags: [...] }).
  • kitMount is now mount.kit.
  • ogygia/rewrite is now ogygia/markup (no alias; change the import).
  • transformHole is removed; ogygia.handle({ transform }) covers region answers and documents alike.
  • regionTtl is now regions: { ttl }.
  • regions.preload is removed; each island's whole graph preloads at its wake.

Added

  • One load scheduler for every download ogygia starts: island code and background work wait for the page's fetchpriority="high" resources (or the first input, or a 2.5 s cap), so islands no longer compete with the hero image.
  • Holes and regions a remote query / command renders see their page's page.url, route, params and server-load page.data (looked up through Kit, only when read), so a server-rendered header can refresh in place ("A lake that can refresh").
  • One DOM ownership model for every ogygia DOM writer — who writes inside an element, its attributes, its children's order and their existence — so a web component's own DOM survives repairs, hole answers and navigations.
  • SvelteKit 3 support, on the APIs late Kit 2 and Kit 3 share.
  • The SSR profiler, ogygia({ profiler }): CPU, network, heap and the browser half of a visit, with a 0–100 page score and on-demand mode (profiler: { onDemand: true }).
  • Devtools, ogygia({ devtools }): an island-graph dock with Page, Hydration, Record, Bytes and Profiler tabs, every vital explained down to the island and line responsible.
  • ogygia/testing: the devtools and the profiler as test primitives.
  • ogygia/router: Kit's routing, programmatic and fully typed; document() renders a complete page from a handle.
  • ogygia/internal/compiler: the compiler as a standalone, bundler-agnostic engine.
  • Frozen pages, ogygia({ freeze: true }): a page renders once on write and is served as stored bytes until a publish thaws it (with edge stitching for per-visitor holes).
  • Fragment federation: one federate(), a remote fragment is a region, and a publish thaws it everywhere.
  • The Ref hub: one identity layer for live classes, stores, functions, snippets and held regions that cross an island boundary.
  • Transportable values: everything devalue rejects now crosses through one serialization seam; unbridgeable values fail at discovery, not in production.
  • A navigation moves regions instead of resetting them (state-delta reconciler); opt-in server-delta navigation renders only what changed (router: { serverDelta: true }).
  • ogygia.handle({ transform }): server transforms that may reshape Svelte-owned markup, restored in the browser before hydration.
  • ogygia/markup and scanRegions(html): the region round trip for an app that runs a third-party SSR pass over the page.
  • On-demand server islands: render: 'deferred' + wake: 'interaction' fetch nothing until the visitor shows intent.
  • prefetch warms a deferred hole's HTML before it swaps.
  • keepFallback(): a server island's answer that keeps the page's fallback.
  • requestEvent(): Kit's RequestEvent inside a server island.
  • isOgygiaPage(): tells shared code which world it runs in.
  • A lake survives Kit hydration on a csr = true page.
  • ogygia({ barrels }): barrel imports become leaf imports at transform time.
  • Zero-config library islands: a dependency declares its island roots in its own package.json.
  • The app's hooks.client.ts init runs on csr = false pages.
  • Server islands ship their component tree's CSS; region CSS obeys Kit's inlineStyleThreshold.
  • Lazy islands download at background priority, so they never fight the first paint.
  • Accessible SPA navigation (focus, announcements) and a reason on every page fetch.
  • when(): flags gate routes, and pick chooses infrastructure; OpenFeature interop (ogygia/flag/openfeature).
  • The build warns when island code reads a context nothing bridges.
  • A recovered or healed island says why, and the discard warning names what Svelte threw.
  • The ogygia MCP server grows to eleven tools, and the bundled AI skill is rewritten.

Changed

  • A <Region of={promise}> waits where the render can: awaited in the server render under Svelte's async mode, streamed as a late region on the server router, or resolved after hydration otherwise.
  • Islands inside a region's HTML always wake on the runtime, csr = true pages included.
  • The page seed ships only when an island reads it, and only the page.data keys the page's islands read.
  • The remote seed ships only for remotes an island on the page can call.
  • Island props reference the page seed instead of copying it, and ride at the end of the body with the module-preload hints.
  • Island hints are fetchpriority="low"; island wakes start after the page has painted.
  • The boot never loads Svelte and is four files fetched in one round trip; the runtime goes right before the page's own JavaScript.
  • Duplicate stylesheet links are dropped.
  • HTML is byte-identical across renders (window-aligned capability expiry, per-request slot ids).
  • The server request path is one request store, one document pass and one walk, and stops allocating per item.
  • A slow or hung page can no longer wedge the profiler or the site, and recordings coordinate across processes.
  • The Vite plugin is a compiler, and region rendering is region-granular.
  • rolldown is no longer a dependency: the compiler parses and bundles through the app's Vite, keeping its 16 MB native binary out of production installs.

Fixed

  • Island entries on pages below the root no longer 404 in builds (Kit's asset() form comes from the build's Kit version).
  • A region moved by another script keeps its in-flight fetch and its woken island.
  • Restored shadow roots keep their sheets across a router navigation.
  • A preloaded load hole stays out of the batch instead of downloading twice.
  • Region URLs are pinned to their page, there is one region per hole across path depths, and a navigated island arrives whole in one swap.
  • A registry's plain imports keep their CSS on csr = false pages; an island inside an island links its own CSS; held regions keep inline sheets.
  • A kept island adopts its server DOM instead of discarding it on every load.
  • An island with children has the same fingerprint and bytes on every render.
  • An island edited while it slept hydrates from its own server markup instead of re-rendering.
  • A hole answer that is not the region's (a redirect, a whole document) is refused.
  • A deferred hole survives Kit rebuilding a client-on document, and outlives a CDN-cached document.
  • A hole's answer morphs over its fallback on every schedule, keeping a web component's attributes and state, unfocused edited fields, and focus visible to assistive tech.
  • Region and hole HTML is parsed aware of declarative shadow DOM.
  • A lake with no island host survives Kit giving up on the document, and a lake's island with props hydrates on a csr = true page.
  • An error page under a csr = false page renders as the Kit-hydrated document it is.
  • A csr = true route that imports a block registry no longer links every block's CSS and chunk, and a csr = false page links only the island CSS it renders.
  • Island entries and the runtime are content-hashed, so a deploy never runs the last build's island code.
  • The runtime's hash module is no longer named fingerprint.js, which ad blockers blocked.
  • A visible island's code waits for the viewport, and a scroll-woken island no longer waits on the scheduler.
  • An island of ours on a Kit document waits until Kit has applied its page; requestEvent() answers under Kit's own render too.
  • goto() from an island on a Kit-booted page navigates through Kit, and a script module reached through an app alias gets the island's page.
  • The router keeps a destination page's inline <style> sheets, follows data-sveltekit-reload's grammar, navigates links inside web components in place, corrects the address bar after a server redirect, and keeps the live <body> on a fallback swap.
  • An unkeyed wrapper around an island survives a sibling inserted above it, and lakes restore by DOM position.
  • A layout's csr world derives from the pages it serves, and a csr = true page under a csr = false layout keeps the layout's chrome.
  • kit.files.routes, kit.outDir, appDir and paths.base are honoured everywhere, and route walkers follow symlinks.
  • Island chunks are emitted only for islands the server bundle can reach.
  • Region fingerprints no longer cost 20 ms per island; the profiler is free when it is not profiling.
  • $app/stores $page works inside every ogygia render root, and a server island's page.url is the referring page.
  • On a csr = true page a server island keeps its server-minted endpoint through Kit's hydration, and server-island HTML is absolutized for nested paths.
  • wake: 'interaction' replays the waking click on the next frame, into the deepest target.
  • A wake: 'none' lake in a +layout.svelte ships its scoped CSS, a lake's or island's scoped CSS keeps its Svelte hash, and the fouc-css ...
Read more

v0.7.0 — placed islands (asRegion) + cross-island context

Choose a tag to compare

@PuruVJ PuruVJ released this 20 Aug 16:23
43f37c4

0.7.0 — 2026-08-20

Two arcs land together. Cross-island context becomes one model on Svelte's own getContext (with a drop-in setContext), and $page.data — streamed load promises and all — reaches islands on csr=false. And region authoring grows two ways to reach imports the import X with { … } form never could: import.meta.og.asRegion(Comp, options) marks any imported component (named / barrel, in .svelte, .ts, and .js), and a .ts registry with { wake } binding is now mountable — placeable by a third-party renderer (e.g. Builder.io's <svelte:component>), not only by region(). Plus a batch of correctness and build fixes.

Added

  • $page.data (+ form / error / status) works inside islands — including STREAMED load
    promises.
    An island reads $page.data through the $app/state shim, but the handle can't reach
    the resolved load data (Kit merges it locally in render.js, never on RequestState), so
    Region.svelte reads Kit's REAL page during SSR and records it; the handle seeds
    data/form/error/status into the same application/ogygia-page script islands already read
    (boundary law: page.data crosses). A load may return a Promise at any level (Kit streaming) — dead on
    csr=false, since Kit never defines its client resolver there. ogygia mirrors the mechanism with its
    own registry: on a real browser navigation each promise is STAGED to a marker (a real pending Promise
    on the client) and a <script>__ogygia_page_resolve(id, ok, value)</script> streams per promise AS
    IT SETTLES — so the shell + pending {#await} paint immediately (FCP is not blocked on the slowest
    promise) and each island's {#await page.data.x} resolves live, fast-first. Kit's own dead csr=false
    resolve tail is drained server-side, so there's no __sveltekit_<hash> is not defined console error.
    A REJECTED promise streams as an error and shows {#await …:catch}; a promise that resolves to a
    value holding MORE promises re-defers them recursively (like Kit). A programmatic fetch (SPA/router —
    no Sec-Fetch-Mode: navigate) can't run streamed scripts, so those promises are awaited server-side
    and revived as already-settled Promises — page.data.x stays a Promise on both paths, and a
    rejection there never crashes the render. The app's universal transport hook applies to the seed
    too, so a load's CUSTOM types round-trip into islands — both plain and inside a streamed promise, not
    just built-in devalue types. Guarded by test/page-defer.test.ts (codec + settle + recursion +
    rejection races + transport), e2e/page-data.ts (stream-level timings + browser hydrate) and
    e2e/page-data-stress.ts (rejection, nested recursion, 12 staggered, non-navigate no-crash, custom
    transport type plain + streamed).

  • Drop-in setContext — adopt with an import swap. Swap import { setContext } from 'svelte'
    for from 'ogygia' and an existing csr=false layout's context reaches child islands with NO other
    change. It does exactly what Svelte's does (same-root + the SSR-nested tree), and on the server also
    records each string-keyed value so the handle emits ONE page-level <script data-ogygia-provide-page>
    marker every island seeds its own getContext('key') from — closing the gap where a plain
    setContext shows on the server but is gone on the client (child islands are separate hydration
    roots). This is the FLAT page root: every island on the page inherits it; a live [ogygia.wire]
    value still reunites to one instance, so a writer island bumping it repaints every reader. For
    scoped/shadowed context use <Provide>, which wraps its subtree and beats the root on the same key.
    node:async_hooks stays server-only (out of the client bundle). Guarded by the /ctx-setcontext
    block in e2e/context.ts.

  • A .ts registry with { wake: … } binding is now MOUNTABLE. Marking a component with { wake }
    in a .ts module (a registry / remote) used to yield a bare held descriptor — a value only region()
    could render. It now yields a real component (the wrapper, with the region metadata attached), so a
    third-party renderer can PLACE it directly (e.g. Builder.io's <svelte:component this={component}>)
    and get the <ogygia-region> shell — while its JS still loads only when the component is placed AND
    its wake schedule fires. It stays a HELD, crossable island too (region() over a live/remote query is
    unchanged), so this is a pure superset. Mirrors what a .svelte with { wake } has always produced;
    a .svelte PLACED island and a .ts HELD island keep distinct identities (one carries a server
    endpoint, the other does not). region: 'raw' is unchanged. Threaded through one shared emitter so
    the .svelte and .ts paths can't drift.

  • import.meta.og.asRegion(Comp, options) — the barrel escape hatch for regions. The
    import-attribute form (import X from './X.svelte' with { … }) only reaches a DEFAULT import of one
    file, so a component you can only get through a barrel (import { Header } from '@design/system')
    couldn't be a region. asRegion marks ANY imported component — named or default — as a region. It
    is a transparent escape hatch: options accept EXACTLY what an import attribute accepts (no
    more, no less — both funnel through one parser) and the output is identical, only importing the
    component by its export name. Every mode works — a placed island (wake), a deferred/live server
    hole (render), a lake (wake: 'none'), a held region (region: 'raw'), or a named preset:

    import { Header, Chart, Block } from '@design/system';
    const HeaderIsland = import.meta.og.asRegion(Header, { wake: 'load' });
    const ChartHole    = import.meta.og.asRegion(Chart, { render: 'deferred', wake: 'visible' });
    const BlockRaw     = import.meta.og.asRegion(Block, { region: 'raw' });   // → region(BlockRaw, …)

    Region identity keys on source#exportName, so two named exports of one barrel are distinct
    regions. Tree-shaking is preserved — a region off a huge mixed barrel ships only its own
    component, not the barrel. Compile construct — rewritten to a hoisted binding import; there is no
    runtime asRegion.

    Works in .svelte, .ts, and .js files. In a .svelte host it mints a placed island; in a
    .ts/.js registry or remote it mints a mountable held binding (wake) or a bare descriptor
    (region: 'raw') — the same records the with { … } form makes there, so a barrel component in a
    .ts registry (e.g. handed to Builder.io's <svelte:component>) is now a one-line macro.

    It is top-level only: const Local = import.meta.og.asRegion(Comp, options) in the instance
    <script>. Misuse is a loud build error — a call nested in a loop / function / block / expression,
    in <script module> or markup; a let/var or multi-declarator binding; a non-imported or
    namespace-imported first argument; or a component already marked with an import attribute (use one
    mechanism, not both).

Changed (breaking)

  • Unified cross-island context. <Context of={ctx} value={v}> + ctx.get() (which walked the
    DOM) is replaced by ONE provider and plain Svelte reads. <Provide values={obj | array}> writes
    serialized values into the DOM (clsx-style: an object, or an array of objects merged left→right,
    falsy skipped), and ogygia seeds each island's hydrate({ context }) from the providers above it —
    so a child island's own getContext('key'), unchanged, reads a (csr=false) layout's context
    across the island-root split. createContext<T>(key, default?) is now optional TYPED sugar over
    the same string key: callable to make a <Provide> entry (theme('dark') → { theme: 'dark' })
    and .get() to read typed — a typed context and getContext('sameKey') are the same value. The
    old keyless createContext() and the <Context> component are gone. Values must be serializable,
    like island props (a [ogygia.wire] value still reunites to one live instance across roots). The
    e2e/context.ts matrix (live reunite, defaults, nested shadowing, lakes, SPA nav, defer/visible)
    passes on the new API, plus a raw-getContext island proving the interop case.

Fixed

  • A csr = false subtree under a csr=true ancestor layout now islands correctly. The transform
    marks csr=true route hosts with a context flag so <Region> degrades to inline there — but Svelte
    context flows to ALL descendants while Kit's csr option is per-node, so an option-less ROOT layout
    (Kit default csr=true) leaked true into a csr = false child subtree: every wake: island there
    silently rendered inline — zero <ogygia-region>, no hydration, no onMount — on a page whose own
    csr is false. The transform now injects the opposite marker (false) into every csr=false route
    host, RESETTING the inherited flag exactly like Kit resolves options ({ ...parent, ...own }); a
    csr=true host below the reset re-shadows with true, so mixed trees work in both directions. The
    csr state threads plugin → transform as a tri-state (true host / false host / non-route).
    Guarded by test/csr-false-reset.test.ts and the /mixed-root fixture + e2e/csr-mixed-tree.ts.

  • ogygia's own injected imports resolve to ogygia's own files, not the importer's package. The
    transform injects ogygia/internal (Region / og_portable) and ogygia/internal/server into a host
    or a generated island wrapper. For a host that lives in a monorepo sub-package which doesn't itself
    depend on ogygia, a bare ogygia/internal failed to resolve (Rolldown failed to resolve import "ogygia/internal" from ".../Toolbar.svelte"). The plugin now resolves those injected imports to
    ogygia's OWN files directly — an absolute path under PKG_ROOT (where the plugin lives),
    src/internal.ts in a source checkout or dist/internal.js in a published install, chosen so...

Read more

v0.6.6

Choose a tag to compare

@PuruVJ PuruVJ released this 19 Aug 09:27

Patch: a portable snippet that captures several names from one import no longer breaks the build.

Fixed

  • A {#snippet} forwarded into an island could emit duplicate imports. When such a snippet's body captures multiple names from ONE import { a, b, c } from './x', ogygia hoists the host import into the synth wrapper it generates for the crossing — but it pushed the whole statement once per captured name, so three names produced three identical import lines and the build failed with Identifier 'a' has already been declared. The hoisted imports are now deduped: each statement is emitted once (it already declares all of its names), preserving the original import verbatim (a snippet-nested island's with { wake: … } marks, and default / namespace forms, survive untouched). Reproduced and guarded by test/portable-snippet-imports.test.ts.

v0.6.3

Choose a tag to compare

@PuruVJ PuruVJ released this 19 Aug 06:13

Patch: same-shell page transitions no longer stutter under a large sidebar.

Fixed

  • A DocsShell sidebar drowned its own page cross-fade. Each nav row carries a per-row view-transition-name (so the og-nav-active chip glides beneath stable labels), which promotes every row to its own transition group. On a same-shell navigation the browser then ran the default group+fade for all of them — dozens per hop (~150 for a 30-row sidebar, ~300 for a large one) — even though the rows are identical and stationary. That flood starves the actual content cross-fade, which janks or reads as "no transition." The rows already share a phnav view-transition-class; every built-in theme now zeros those row animations (animation: none), so an identical row snaps invisibly while the chip keeps its slide and the root content fade is no longer drowned. Measured on the docs playground: 295 → 10 running animations, slide and fade both intact. Applied across every built-in theme.

v0.6.2

Choose a tag to compare

@PuruVJ PuruVJ released this 19 Aug 05:21

Patch: content bodies ship their own scoped CSS, a static server island no longer 404s on nav, and a shell-change page transition no longer stutters.

Fixed

  • Content-body CSS on csr=false pages. A .svx/.md body's own scoped <style> lived only in the server bundle (the leak-free corpus never enters the client graph), so it shipped on no page stylesheet and doc content rendered browser-default in production. The build now svelte-compiles each content module's scoped CSS from its post-mdsvex source (so :global is resolved and the scoped hash matches the SSR'd HTML) and emits it as a content-addressed client asset, keyed in the region-deps handoff; Region.svelte links it via <link data-ogygia-region-css>. Per-page cost is only the CSS of the docs actually rendered, and it survives SPA nav. Covered by e2e/content-css.ts.

  • Static server-island 404 on navigation. A render: 'deferred' island with no wake has no client module, but Region.svelte still emitted the region id as the DOM entry. The router's next-page module-warmer imports every entry="…", so that bare id fetched /<id> → 404 (only on nav — the warmer runs on prefetch). Static server islands now carry entry=""; the hole still fetches through its signed endpoint (minted from the id, unchanged). The same rule covers held deferred regions.

  • Shell-change page-transition stutter. A view-transition-name lifts its element out of the root cross-fade into a standalone group. Navigating between a page with a sidebar of many named nav rows and one with none left every name orphaned, each running a solo enter/exit over a holed-out root snapshot — a stutter, worst in dev. The router now folds orphaned view-transition-names (present on only one of the two pages) into the page cross-fade for that navigation, and restores them after. Names present on both pages (a same-shell highlight slide) are kept untouched.

Full stack green: 809 unit tests + 40 e2e checks.