All notable changes to Pyra.js are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- All 5 example apps were unrunnable - every
pyra.config.tsinexamples/was missingadapter: createReactAdapter(), causingpyra dev/build/startto fail immediately with a missing-adapter error; all configs now import and set the adapter correctly - Example
package.jsonfiles missing@pyra-js/adapter-react- all 5 example apps now declare@pyra-js/adapter-react: workspace:*as a dependency so the adapter resolves correctly when running from the monorepo - HMR error overlay - compilation and route-scan errors are now broadcast to the browser as
{ type: "error" }WebSocket messages; an in-browser overlay (dark card, error badge, file pill, stack trace, ESC to dismiss) replaces silent console-only failures; overlay auto-dismisses on the next successful update or reload - Parallel compilation guard - concurrent requests to the same uncached route no longer each spawn a duplicate esbuild process; a
pendingBundlesmap inbundler.tsand apendingServerCompilesmap indev-compiler.tsdeduplicate in-flight compiles so late arrivals await the same Promise - Event loop blocking in static file handler -
dev-static.tsservePublicFile()switched fromfs.readFileSynctofs.promises.readFile;dev-server.tsCSS and static file handlers likewise converted tofs.promises.statandfs.promises.readFile, unblocking concurrent requests during large file reads
<Link>used for all internal navigation in examples - all layout nav links and intra-app page links acrosspyra-blog,routing-showcase,ssg-cache, andmiddleware-authnow use<Link>from@pyra-js/adapter-reactinstead of raw<a href>, enabling client-side SPA navigation without full page reloads- Dead artifacts removed from
examples/- removedKubo/(orphaned dist artifact),basic-ts/(pre-routing SPA era),test-config/(internal test fixture),example.sh(empty file), and 6 stalepyra.config.*.tsfiles (basic,full,library,minimal,mode,react) that described a superseded config API;QUICK_START.mdandUSAGE.mdalso removed as they documented the same obsolete format examples/pyra.config.reference.tsadded — single accurate reference file documenting every realPyraConfigfield with inline comments, including adefineConfigFnmode-aware example at the bottom
pyraFramerMotion()plugin in@pyra-js/core- solves the Framer Motion + SSR incompatibility at the framework level- Implements the new
headInjectionplugin hook to inject<style id="__pyra_fm">[data-projection-id]{opacity:1!important;transform:none!important}</style>into every SSR page's<head>, keeping all Framer Motion elements visible during SSR and before hydration regardless of theirinitialprop
- Implements the new
<FramerMotionReady>component in@pyra-js/adapter-react, companion component forpyraFramerMotion(); place it in your root layout to remove the SSR override after React hydrates and Framer Motion's ownuseLayoutEffecthas initialized, usingrequestAnimationFrameto ensure correct sequencingheadInjection?(): stringhook on thePyraPlugininterface in@pyra-js/shared- allows plugins to inject arbitrary HTML into<head>on every SSR page render, prepended before the adapter's own head tags; wired into both the streaming and buffered render paths in the dev server (dev-ssr.ts) and production server (prod/prod-server.ts)- Dev server compile cache warming - on startup, the dev server now pre-compiles all route and layout files in the background using
Promise.allSettled, eliminating the ~280ms cold-start delay on the first browser request - SSR-safe hooks in
@pyra-js/adapter-react-useLocation(),useParams(), anduseRouteError()now return safe defaults during server-side rendering instead of crashing withwindow is not defined;useLocation()returns{ pathname: '/', search: '', hash: '', searchParams: new URLSearchParams() }on the server,useParams()returns{}, anduseRouteError()returnsnull
- Streaming SSR - pages can now stream HTML to the browser as React renders, reducing time-to-first-byte for data-heavy pages
renderToStream?(component, data, context): Readableadded to thePyraAdapterinterface - optional; servers fall back to the existing bufferedrenderToHTML()path when not implemented- React adapter implements
renderToStream()using React 18'srenderToPipeableStream, piping output through a Node.jsPassThroughstream - Dev server (
dev-ssr.ts) detectsadapter.renderToStreamand takes the streaming path: sendsrawBefore(everything up to<!--pyra-outlet-->) immediately, streams React output as chunks arrive, then appends hydration scripts andrawAfteron stream end - Production server takes the same streaming path when the adapter supports it
buildDeferredHeadScript()helper indev-ssr.ts-pushHead()calls that happen during streaming (after<head>has already been sent) are collected and injected via an inline<script>that appends them todocument.headat runtime
- Response compression in
@pyra-js/core- gzip compression for HTML, JSON, CSS, and JavaScript responses in the production server- New
packages/core/src/prod/prod-compress.ts-compressResponse()detectsAccept-Encoding: gzip, compresses compressible content types, setsContent-Encoding: gzipandVary: Accept-Encodingheaders compressoption added toDevServerConfig(defaulttrue) - set tofalseto disablesendResponse()utility inProdServer- centralised helper replacing scatteredsendWebResponse()call sites, handles both streaming and buffered responses and applies compression uniformly
- New
- Test suites across
@pyra-js/core- new vitest suites covering previously untested subsystemscors.test.ts- CORS header logic for allowed origins, credentials, preflight responses, and theorigin: falsedisable pathrender-mode.test.ts-resolveRouteRenderMode()covering SSR/SPA/SSG resolution from module exports and global config defaultsprod-assets.test.ts- production static file serving, cache header generation for hashed vs. non-hashed assets, and 404 handlingprod-compress.test.ts- gzip and identity response paths,Accept-Encodingnegotiation, and compressible content-type detectiontracer.test.ts-RequestTracerstart/end stage lifecycle, bottleneck detection thresholds, andServer-Timingheader format
- STABILITY document (
STABILITY.md) - documents API stability guarantees and the pre-1.0 deprecation policy
prod-server.tsrefactored into thesrc/prod/directory for better code visibility -prod-assets.ts,prod-matcher.ts,prod-html.ts, andprod-server.ts;packages/core/src/prod-server.tsretained as a thin re-export shim so external callers require no changes- Streaming responses in
ProdServernow useReadable.fromWeb()to pipe directly to the HTTP response without buffering the full body in memory first
- Browser-safe client entry point in
@pyra-js/adapter-react- newclient.tsexports only components and hooks that are safe to bundle for the browser; intentionally excludescreateReactAdapterandfast-refresh-pluginwhich import Node.js built-ins (path,fs,module) that cannot be resolved in a browser bundle- esbuild picks this file automatically when
platform: "browser"is set via the"browser"condition in the packageexportsfield client.tsadded as a named entry point in the tsup build config so it is emitted asclient.jsindist/
- esbuild picks this file automatically when
compileForServerinpackages/core/src/dev/dev-compiler.tsnow correctly externalizes React - previously React was being bundled into server compilation output instead of being treated as an external peer dependency_requireinfast-refresh-plugin.tsis now created lazily inside the function body instead of at module scope - the file can now be imported in browser bundles without triggering a Node.jscreateRequirecall at import time@babel/coreandreact-refresh/babeladded to theexternallist inpackages/core/src/bundler.ts- these are Node.js-only packages and must not be bundled into browser output- Node.js built-in modules added to the
externallist inpackages/core/src/bundler.tsas a fallback - prevents esbuild from attempting to bundle built-ins when processing files that conditionally import them - Image adapter test corrected to return the
pyra-react-fast-refreshplugin from the mock adapter
- React Fast Refresh - component-level hot updates in the dev server replace full-page reloads, preserving React component state, scroll position, and app context on every file save
- New
fast-refresh-plugin.tsin@pyra-js/adapter-react- esbuildonLoadplugin that runsreact-refresh/babelvia@babel/coreon every.tsx/.jsx/.ts/.jsfile outsidenode_modules, inserting$RefreshReg$and$RefreshSig$registration calls; JSX and TypeScript transforms are still handled by esbuild after the plugin returns the modified source getHMRPreamble?(): stringadded to thePyraAdapterinterface in@pyra-js/shared- adapters return a<script type="module">tag that initialises the HMR runtime in the browser before the hydration script's static imports execute, ensuring globals are in place when component registration calls fire/__pyra_refresh_runtimeendpoint in the dev server - lazily resolvesreact-refresh/runtimefrom the user's project root viacreateRequire, bundles it to ESM with esbuild once per session, and serves it withCache-Control: no-cache; returns 404 ifreact-refreshis not installedcanFastRefresh(filePath, routesDir)helper indev-hmr.ts- returnstruefor.tsx/.jsx/.ts/.jsfiles that are not server-only route files (route.*,middleware.*); drives theupdatevsreloaddecision in the file watcherwindow.__pyra_hmr_modules- a regular<script>tag (synchronous, runs before deferred module scripts) injected per page indev-ssr.tslisting all client module URLs (/__pyra/modules/...) for the current page and its layouts; kept in sync after client-side navigations in the hydration scriptwindow.__pyra_refresh— set by the RFR preamble to thereact-refresh/runtimeinstance so the HMR client can callperformReactRefresh()without importing the runtime again@babel/core ^7.24.0andreact-refresh ^0.14.0added as dependencies of@pyra-js/adapter-react
- New
<Head>component in@pyra-js/adapter-react— manage document head tags (title, meta, link, etc.) from within any page or layout component<ClientOnly>component in@pyra-js/adapter-react— suppresses SSR rendering of its children; useful for components that depend on browser-only APIs and would otherwise throw duringrenderToString()<Form>component in@pyra-js/adapter-react— enhanced form element with type-safe props<NavLink>component in@pyra-js/adapter-react— like<Link>but automatically applies an active CSS class when itshrefmatches the current pathname; drops in as a replacement for<Link>in nav bars and sidebars- Routing hooks and utilities in
@pyra-js/adapter-reactuseLocation()- returns the current URL as aURLobject, reactive to client-side navigationsuseRouter()- exposesnavigate(href)and the current params object; thin wrapper aroundwindow.__pyra- Additional routing helpers exported from the package index
- Deployment documentation (
docs/deployment.md)
- Adapter-agnostic core -
DevServer,build(), andProdServerno longer have any knowledge of React; the adapter must be supplied by the application viapyra.config.ts@pyra-js/adapter-reactremoved from@pyra-js/clidependencies - the CLI no longer bundles the React adapter; projects must install it separately (all templates already do)- Passing
adapter: createReactAdapter()inpyra.config.tsis now the only way to opt into React SSR; omittingadapteris an error forpyra dev,pyra build, andpyra start(existing behaviour, now enforced without a React fallback)
- HMR file watcher now distinguishes between two update strategies for the
changeevent.tsx/.jsx/.ts/.jsfiles that are not server-only route files → broadcasts{ type: 'update' }(triggers React Fast Refresh in the browser)- Everything else (CSS, JSON,
route.*,middleware.*, config files) → broadcasts{ type: 'reload' }(full page reload, same as before) - File
addevents always broadcast{ type: 'reload' }(route graph may have changed)
- HMR client script updated to handle
{ type: 'update' }messages - re-imports all tracked page and layout modules with a?__hmr=<timestamp>cache-bust query, then callswindow.__pyra_refresh.performReactRefresh(); falls back towindow.location.reload()ifwindow.__pyra_refreshis not defined (non-React adapter) or if the re-import throws bundleFile()inpackages/core/src/bundler.tsaccepts an optionalextraPlugins: esbuild.Plugin[]fourth parameter - prepended before the PostCSS plugin; used to pass adapter-provided esbuild plugins (e.g. the RFR transform) to all client-side bundles from both/__pyra/modules/*and/__pyra/styles/*endpoints
FormPropstype error in@pyra-js/adapter-react- corrected incorrect prop type annotation that caused a TypeScript compile error when using the<Form>component- Type errors in
PyraAdapterinterface resolved - corrected mismatched signatures that surfaced after the adapter-agnostic refactor
- Client-side navigation (CSN) - same-layout routes now transition without a full page reload
- New
/_pyra/navigate?path=...JSON endpoint in both dev and prod servers, runs middleware +load(), returns{ data, clientEntry, layoutClientEntries, routeId } - Dev server: synthetic
fakeReqviaObject.create(req)ensuresctx.urlreflects the navigated path soload()reads the correct query params - Prod server: manifest-driven, same response shape as dev
- Middleware redirects (3xx) returned as
{ redirect: location }so the client performs a full navigation to the correct destination <Link>component added to@pyra-js/adapter-react- intercepts same-origin clicks, passes through modifier-key clicks (Cmd/Ctrl/Shift/Alt) for native browser behavior, callswindow.__pyra.navigate()for client-side transitionsgetHydrationScript()in the React adapter now generates a persistentPyraAppshell component usinguseState/useEffectinstead of a one-shothydrateRoot()calluseState(() => InitialComponent)+useState(initialData)hold the current page component and datauseEffectregisterswindow.__pyra.navigate(href)and apopstatehandler for Back/Forward button support- Layout chain comparison: if
nav.layoutClientEntriesdiffers from the boot-timepageLayouts, falls back tolocation.href(full reload) - keeps layout boundaries clean without complex diffing window.scrollTo(0, 0)on each client-side navigation
- New
- React Compiler wizard prompt updated from
"Enable React Compiler?"to"Enable React Compiler? (beta - adds Babel build step, benefits are client-side only)"- makes the tradeoffs visible before the user opts in
- Tailwind CSS scaffolding in
create-pyrafor full-stack projects now prepends@tailwinddirectives to the existingstyle.cssinstead of creating a separateindex.css- preserves all custom template styles (nav, cards, hero, etc.) that were previously lost - Tailwind
index.csswas previously written tosrc/but full-stack layouts import fromsrc/routes/, file is no longer created separately; directives are merged into the existingstyle.cssinsrc/routes/
- Router and React Compiler options in
create-pyrawizard, new post-copy patching system inpackages/create-pyra/src/patches.tsapplies targeted file modifications after the base template is copied, avoiding a explosion of template directories- React SPA mode now prompts for a client-side router: None, React Router v7 (
react-router: ^7.0.0), or TanStack Router (@tanstack/react-router: ^1.0.0) - React Router patch: rewrites
main.tsxwithcreateBrowserRouter, convertsApp.tsxinto a layout shell with<Outlet />, and createssrc/pages/Home.tsx - TanStack Router patch: rewrites
main.tsxwith a fully code-based router, removesApp.tsx(root route replaces it) - React (both SSR and SPA) now prompts to enable the React Compiler, adds
babel-plugin-react-compilerandesbuild-plugin-babeldevDependencies and rewritespyra.config.ts/jswith a working Pyra plugin block
- React SPA mode now prompts for a client-side router: None, React Router v7 (
- Separator lines in request trace terminal output - each
toDetailedLog()block is now wrapped with a──────────────────────────────────────rule above and below, making individual requests visually distinct in busy dev server output - Compat shim packages (
packages/compat-pyrajs-*/) - new versions of the oldpyrajs-*package names that re-export from@pyra-js/*so existing projects continue to work without code changespyrajs-shared→ re-exports from@pyra-js/sharedpyrajs-core→ re-exports from@pyra-js/corepyrajs-adapter-react→ re-exports from@pyra-js/adapter-reactpyrajs-cli→ re-exports from@pyra-js/cli; includes abin/pyra.mjsshim so thepyrabinary continues to resolve- Each compat package ships a
README.mdwith a deprecation notice and a migration guide
- All four packages renamed to the
@pyra-jsnpm scopepyrajs-shared→@pyra-js/sharedpyrajs-core→@pyra-js/corepyrajs-adapter-react→@pyra-js/adapter-reactpyrajs-cli→@pyra-js/cli
defineConfigand user-facing types (RequestContext,Middleware,ErrorPageProps,CacheConfig,PrerenderConfig) are now re-exported from@pyra-js/cli- application developers only ever need to import from@pyra-js/cliand never from@pyra-js/shareddirectly- All generated project templates (
create-pyraandpackages/cli) updated to importdefineConfigfrom@pyra-js/cli
babel-plugin-react-compilerversion corrected from^19.0.0(non-existent) to^1.0.0- React Compiler reached stable 1.0 in October 2025 under its own versioning scheme
- Dev server proxy middleware - configure upstream request forwarding via
server.proxyinpyra.config.ts- New
packages/core/src/dev/dev-proxy.tswithmatchProxyRule()andhandleProxyRequest()- zero new dependencies, uses Node's built-inhttp/httpsmodules - Config shape:
proxy: { '/api': 'http://localhost:4000' }(string shorthand) or{ target, changeOrigin?, rewrite? }(object form) changeOrigin: truerewrites theHostheader to the upstream host (needed for virtual-host upstreams)rewriteapplies to the path only; query string is preserved and forwarded unchanged- Proxy check runs after internal
/_pyra/*and/__pyra/*endpoints, before static file serving and route matching - Returns
502 Bad Gatewaywith a plain-text body on upstream connection errors
- New
pyra graphHTML visualization completely overhauled- Directed arrows on edges show dependency direction at a glance
- Click any node to focus it, non-connected nodes and edges are dimmed to 15% opacity; press Escape or click background to deselect
- Workspace color palette - internal nodes are colored by which workspace package they belong to; external nodes remain gray
- Dynamic workspace legend built from the package color map
- Center gravity force added to simulation (
vx -= x * 0.04) - prevents nodes drifting to the canvas edges - Layered initial seeding, internal nodes placed on an inner ring, external nodes on a larger outer ring for faster convergence
- Edge hover reveals the dependency version label at the edge midpoint
- In-degree badges, nodes with more than one incoming dependency show a blue count badge
- Labels hidden for external nodes unless hovered; tooltip flips side when near viewport edges
- Reduced spring constant (
k70→35) and node radii (internal 7→5, external 5→3) for a denser, more readable layout
- Dead
featuresfield removed fromPyraConfiginpackages/shared/src/types.ts-features.cssModules,features.typeCheck, andfeatures.jsxwere declared but never read by any package - Dead
esbuildfield removed fromPyraConfiginpackages/shared/src/types.ts- was typed asRecord<string, any>and never consumed; esbuild options are passed through the build system internally
vitest.workspace.tsreverted to a plain array export -defineWorkspacewas removed in Vitest 4 and its presence caused a TypeScript error (Module '"vitest/config"' has no exported member 'defineWorkspace')- Deleted
packages/core/src/__tests__/image-plugin.test.ts- the test suite was failing in CI on all Node versions (18, 20, 22) due to Vite 7's node environment not matching the"import"export condition inpyrajs-shared/package.json; the remaining test coverage for the image plugin is provided by the integration tests
pyra doctorno longer reports "Entry point not found: src/index.ts" in full-stack (file-based routing) projects, the entry check is now skipped when a routes directory is detected, since file-based projects do not use a single entry filepyra doctorTypeScript check no longer triggers Node.js DEP0190 deprecation warning ("Passing args to a child process with shell option true"),runTscCheck()now spawnscmd.exe /c tsc.cmd --noEmiton Windows instead of usingshell: true, eliminating both the warning and the follow-onEINVALspawn error
- Patched GHSA-mw96-cpmx-2vgc (high severity) - Rollup arbitrary file write via path traversal in versions
>=4.0.0 <4.59.0; resolved by adding"rollup": "^4.59.0"topnpm.overridesin the rootpackage.json
- Deprecated
frameworkfield removed fromPyraConfiginpackages/shared/src/types.ts- theadapterfield fully replaces it and has been the intended API since v0.3
build.splittingis now read fromconfig.build?.splittinginstead of being hardcoded totrue, applies to both the SSR client build (build-orchestrator.ts) and the SPA build (buildSPA.ts); server build remains hardcoded tofalseas Node.js ESM cannot load split chunks dynamically
packages/cli/src/bin.tsextracted into per-command files underpackages/cli/src/commands/commands/dev.ts- exportsDevOptionsanddevCommand()commands/build.ts- exportsBuildOptionsandbuildCommand()commands/start.ts- exportsStartOptionsandstartCommand()commands/init.ts- exportsInitOptionsandinitCommand()bin.tsreduced from ~625 lines to ~155 lines; now a pure wiring file that resolvessilent/colorand delegates to each command function- Follows the same pattern already used by
commands/graph.tsandcommands/doctor.ts
packages/core/src/build.tssplit into focused modules underpackages/core/src/build/build-utils.ts-routeIdToSafeName(),getAncestorDirIds()pure string utilitiesbuild-client.ts-buildClientOutputMap(),buildClientLayoutOutputMap()esbuild metafile correlationbuild-server.ts-buildServerOutputMap(),buildServerMwLayoutOutputMap()server entry path resolutionbuild-manifest.ts-assembleManifest(),buildEmptyManifest(),getMimeType(),DEFAULT_SHELLbuild-prerender.ts-buildPrerenderAssetTags()SSG asset tag generationbuild-report.ts-printBuildReport()and private helpers (estimateGzipSize,getSharedChunks,formatSize,countFilesRecursive)build-orchestrator.ts- mainbuild()functionbuild.tsreplaced with a 3-line re-export shim; all external imports remain unchanged
packages/core/src/prod-server.tssplit into focused modules underpackages/core/src/prod/prod-matcher.ts-MatchResult,buildMatcher()trie-based manifest route matcherprod-assets.ts-buildCacheControlHeader(),getCacheControl(),getContentType(),buildAssetTags()static asset helpersprod-html.ts-DEFAULT_SHELL,getErrorHTML(),get404HTML()HTML string generatorsprod-server.ts-ProdServerclass importing from the three helpers aboveprod-server.tsatsrc/root replaced with a 4-line re-export shim; all external imports remain unchanged
- CORS support - configure cross-origin resource sharing via the
corsfield inpyra.config.ts- New
CorsConfigtype inpackages/shared/src/types.ts:origin,methods,allowedHeaders,exposedHeaders,credentials,maxAgefields packages/core/src/cors.ts—buildCORSHeaders()andapplyCors()helpers; origin resolution supports wildcard, string, and array allow-lists; automaticOPTIONSpreflight handling- Dev server and production server both apply CORS headers via
applyCors()before routing
- New
- Static HTML CSS injection in dev server — prerendered/static HTML pages now receive
<link rel="stylesheet">tags for co-located CSS imports, consistent with SSR pages - HMR client CSS injection - extracted CSS is now injected into the HMR client bundle so hot-reloaded pages receive stylesheet updates
packages/core/src/dev-server.tssplit into eight focused modules underpackages/core/src/dev/dev-compiler.ts— on-demand esbuild compilationdev-hmr.ts— WebSocket HMR server and client injectiondev-dashboard.ts—/_pyradashboard and API endpoint handlersdev-static.ts— static file andpublic/directory servingdev-api.ts— API route dispatch and method validationdev-routes.ts— page route matching and SSR pipeline entrydev-errors.ts— error boundary rendering and 404 handlingdev-ssr.ts— core SSR assembly (load → render → shell → inject assets)- Dead code removed;
dev-server.tsnow re-exports and wires the modules together
- TypeScript typecheck errors in
Image.test.tsx— correctedmockContext,renderToHTML, adapter, and React type annotations across multiple test helpers - Image plugin tests - added missing manifest fixture to
image-plugin-tests.ts - Middleware tests - corrected assertions after middleware refactor
- Module resolution and path alias support via
ResolveConfigis now wired to esbuild in both dev and production buildsbundler.ts: newbuildEsbuildResolveOptions()helper translatesResolveConfigto esbuildalias,resolveExtensions, andmainFieldsoptions; alias values are resolved to absolute paths automaticallydev-server.ts: all fourbundleFile()call sites now passconfig.resolvebuild.ts:buildEsbuildResolveOptions()spread into both client and serveresbuild.build()calls
RequestTracer.setDetail(detail)method - annotates the most recently closed stage with a detail string without opening a new span; used to attach the matched route ID to theroute-matchstage after the router returnsMetricsStore.isActiveBuild()method - returns true whenstartBuild()has been called without a matchingfinishBuild(); used to coordinate build lifecycle across the file watcher and request handler- Build metrics now fully wired in dev server
startBuild()called in thechangeandaddwatcher handlers when a source file is saved- Orphaned builds (two rapid saves with no intervening request) are closed before the next
startBuild()viaisActiveBuild() finishBuild()called after the response is sent for the first routed request following a file change;totalDurationspans the full file-save-to-response cycle
- Dashboard "Recent Requests" section - live table polling
/_pyra/api/tracesevery 2 seconds- Color-coded method badges (GET, POST, PUT, PATCH, DELETE)
- Color-coded status badges by class (2xx, 3xx, 4xx, 5xx)
- Per-request pipeline stage breakdown with slow-stage highlighting (yellow above 50% of total, red above 80%)
- Relative timestamps ("3s ago", "1m ago")
- Matched route ID shown below the URL when the pattern differs from the path
- Docs:
docs/dashboard.md,docs/request-context.md,docs/cookies.md,docs/plugins.mdx
- Double
route-matchentry in request traces - the dev server was opening two separate spans for route matching; the second (zero-duration) span is now replaced withtracer.setDetail(), attaching the route ID to the first span and keepingextractRouteId()correct - Dashboard empty state messages used em dashes; replaced with commas for consistency
- Dashboard stat cards show
--instead of0when no build data exists yet, avoiding misleading zeros on initial load - Build history rows now include a file count column
- Dashboard "Recent Requests" and "Build History" empty states distinguish between the two conditions with separate messages
-
CSS pipeline fix in dev server -
import './style.css'in layout/page files now works without Flash of Unstyled Contentbundler.ts: newcssOutputCachestores CSS extracted by esbuild separately from the JS bundle; removed the olddocument.createElement('style')injection that caused FOUC- New
getCSSOutput(filePath)export returns cached CSS for a given entry file dev-server.ts: new/__pyra/styles/*endpoint serves extracted CSS as propertext/cssresponseshandlePageRouteInnereagerly callsbundleFile()for each layout + page before assembly, then injects<link rel="stylesheet" href="/__pyra/styles/...">tags into<head>via<!--pyra-head-->style.cssmoved back tosrc/routes/(co-located with the layout) in all full-stack templates;import './style.css'restored as a standard ES module import
-
Package READMEs:
packages/adapter-react/README.md,packages/core/README.md,packages/shared/README.md- each tailored to its audience (end-user, internal/contributor, type reference) -
.vscode/settings.json- excludespackages/cli/templates/**from Tailwind CSS extension scanning, preventing false "unable to load config" errors caused by the extension attempting CommonJSrequire()on ESM-only template config files
create-pyrarendering mode prompt labels updated from "SSR / SPA" to "Full-stack / Frontend (SPA)" -valuefields ("ssr"/"spa") are unchanged so template selection is unaffectedpackages/clidev:linkscript changed frompnpm link -gtonpm link- now consistent withcreate-pyra'sdev:link, makingnpm link pyrajs-cliwork correctly in generated test projects- Full-stack template CSS redesigned across all six templates (
create-pyraReact/Preact TS/JS andpyrajs-cliReact TS/JS fullstack) to match the Pyra visual identity
vitest.workspace.ts: removeddefineWorkspaceimport that broke TypeScript in Vitest 4 (the export was removed in Vitest 4; workspace files now export the array directly)
- Image optimization plugin (
pyraImages()) - framework-agnostic, lives entirely inpackages/core- Activate via
plugins: [pyraImages()]inpyra.config.ts sharpis an optional peer dependency - if not installed, a warning is printed and the endpoint returns 501 with install instructions- Dev server: on-demand
/_pyra/image?src=&w=&format=&q=endpoint with 60-second in-memory cache - Production build: variants pre-generated at build time into
dist/client/_images/with content-hashed filenames - Manifest:
imageskey maps source paths toImageManifestEntrywith all variant metadata - Prod server:
/_pyra/imagereads manifest and serves pre-built files withimmutablecache headers - Config options:
formats(default['webp']),sizes(default[640, 1280, 1920]),quality(default80),cacheDir - Plugin hooks
buildStartandbuildEndare now wired inbuild.ts(previously defined but never called)
- Activate via
- React
<Image>component (packages/adapter-react) — pure URL builder, no processing logic- Renders
<picture>with<source>per format (avif first for best compression, then webp) and<img>as fallback - Supports
widths,formats,quality,sizes,loading,className,styleprops
- Renders
public/directory handling — static assets now served transparently at the root URL (like Vite)- Dev server:
resolvePublicFilePath()checkspublic/before route matching; files served withCache-Control: public, max-age=3600 - File reads use
Buffer(binary-safe) — fixes potential corruption of images and fonts in SPA static fallback - Production build:
public/contents copied todist/client/at the end of the build
- Dev server:
- SPA rendering mode option in
pyra initwizard — after selecting React, users are now asked whether the project is Full-stack (SSR) or Frontend (SPA)- Full-stack →
react-ts-fullstack/react-js-fullstacktemplate (file-based routing, server required) - SPA →
react-ts-spa/react-js-spatemplate (entry:config, no server required)
- Full-stack →
- New CLI templates:
react-ts-spaandreact-js-spapyra.configwithentry:pointing tosrc/main.tsx/src/main.jsxsrc/App.tsx/src/App.jsxwith minimal starter componentpublic/favicon.svgincluded
public/favicon.svgadded to all CLI templates (vanilla-ts,vanilla-js,react-ts-fullstack,react-js-fullstack,react-ts-spa,react-js-spa)public/favicon.svgadded to allcreate-pyratemplates (vanilla-ts,vanilla-js,react-ts,react-js,react-spa-ts,react-spa-js,preact-ts,preact-js,preact-spa-ts,preact-spa-js)- Favicon
<link>tag added toindex.htmlin all SPA/vanilla templates (bothpackages/cliandpackages/create-pyra) - Favicon
<link>tag added toDEFAULT_SHELLinpackages/adapter-react— full-stack (SSR) projects now include the favicon in the generated HTML shell
PyraPlugin.buildEndsignature updated: now receives{ manifest, outDir, root }context object instead of no arguments
- SPA production build pipeline -
pyra buildnow produces static output for entry-based projects- Detected when
config.entryis a string (e.g.entry: 'src/main.tsx') with no file-based routing index.htmltransformed at build time: dev-time<script type="module" src="...">tags removed, hashed script and CSS injectedpublic/directory copied todist/if present- Build report shows per-file sizes with gzip estimate - same style as SSR report
- SSR/SSG build path is completely unchanged - SPA detection is an early return before any route scanning
- Detected when
spawnPM()increate-pyranow usescmd.exe /c <pm> installon Windows instead ofshell: truewith.cmdextension - fixes dependency installation failing silently after the DEP0190 fix in 0.13.0
- Node.js DEP0190 deprecation warning (
Passing args to a child process with shell option true) no longer appears when runningcreate-pyracommandExists()changed toshell: false-where.exe/whichare real executablesspawnPM()changed toshell: falsewithcmd.exe /con Windows, plain spawn on Unix
packages/core/src/build.tssplit into focused modulesbuild.ts- SSR/SSG orchestrator onlybuildSPA.ts- SPA static build (self-contained with its own helpers)types.ts-BuildOrchestratorOptionsandBuildResultinterfaces (re-exported fromindex.ts)
-
PostCSS integration in core - Tailwind CSS now works in both dev and production builds
css-plugin.ts- esbuild plugin that loads PostCSS dynamically from the user's project (no new dependency inpyrajs-core)- PostCSS config auto-detected (
postcss.config.js/.cjs/.mjs) with support for both object and array plugin formats - CSS through esbuild bundler (imported from JS modules) processed via
createPostCSSPlugin() - CSS served directly as static files in dev server processed via
runPostCSS() - PostCSS config cached per project root to avoid re-loading on every request
-
dev:linkanddev:unlinkscripts tocreate-pyra/package.jsonfor easy local testing. -
CLI templates directory (
packages/cli/templates/) with all four template variants:react-ts-fullstack/- TypeScript fullstack SSR template with layout, page, about page, health API routereact-js-fullstack/- JavaScript fullstack SSR template (same structure, no TypeScript)vanilla-ts/- Minimal vanilla TypeScript SPA templatevanilla-js/- Minimal vanilla JavaScript SPA template
- Tailwind prompt in
create-pyrawizard simplified from a 3-option select (none / basic / shadcn) to a yes/no confirm- Removed shadcn preset - shadcn setup requires manual steps beyond CSS config
- Removed
TailwindPresettype andgenerateShadcnTailwindConfig()helper - Summary row now shows "Yes" / "No" instead of preset name
dev:linkincreate-pyranow usesnpm linkinstead ofpnpm link --global- avoidsERR_PNPM_NO_GLOBAL_BIN_DIRerror when pnpm global bin dir is not configured
- Updated all template CSS to use Pyra brand colors (
#e63946/#f4845f) - replaces old purple/blue and cyan/blue gradients - Added polished CSS to all fullstack SSR templates (nav bar, hero section, feature cards, footer)
- Install dependencies prompt in
create-pyrawizard, users are now asked whether to install instead of auto-installing - TTY detection in
create-pyra- shows a clear error with alternative commands when stdin is not a TTY (fixes crash on Windows when running vianpm create)
create-pyrano longer crashes withEBADFwhen launched throughnpm createon Windows/Git Bash
- Replaced
@inquirer/promptswith@clack/promptsincreate-pyrafor a cleaner interactive wizard experience - Updated architecture documentation
theme.tsincreate-pyra- color palette constants and styled progress indicatorstree.tsincreate-pyra- file tree formatter for post-scaffold output
- Corrected prompt flow issue after migrating to clack
- Render mode system (
ssr,spa,ssg) configurable globally or per-route render-mode.tsin core - resolves per-route render mode from exports vs global config- Render mode branching in build pipeline with SPA fallback support
- Render mode branching in production server
- Rendering mode prompt in
create-pyrawizard - Preact framework support -
preact-ts,preact-js,preact-spa-ts,preact-spa-jstemplates - Project templates for
create-pyra:react-ts,react-js,vanilla-ts,vanilla-js
- Updated
create-pyratemplates to account for SSR/SPA/SSG rendering modes - Updated types to include new rendering engine types
- Refactored
pyra initcommand and scaffold system - Exported
renderModevia config loading
- Corrected
replaceAllerror insidecreate-pyra
- Comprehensive documentation: middleware, SSR, layouts, API routes, CLI, adapters, request tracing
create-pyrareadme
- Updated package versioning across all packages (
pyrajs-cli0.10.1,create-pyra0.11.0)
create-pyrastandalone package - interactive project scaffolding vianpm create pyra- GitHub Actions CI workflows
- Publish script for package releases
- Base
tsconfig.jsonat project root with project references - Pyra logo and brand colors in
create-pyrawizard - Example projects in
examples/folder
- Rebuilt types to align with new root tsconfig
- npm strict publish issue resolved via bin wrapper
- Error boundary types (
ErrorPageProps,ErrorModule) error.tsxand404.tsxroute file conventions- Error handling in dev server - renders nearest error boundary with full stack traces
- Error handling in build pipeline - 404 entries included in production builds
- Error handling in prod server - generic error pages (no stack traces in production)
- Route collision detection in scanner
- Graceful shutdown with
inflightCounttracking and 10-second drain timeout
- Improved terminal styling - Pyra text in red via chalk, cleaner default action display
- Dynamic versioning for CLI template dependencies
- Templates now use dynamic versioning for
pyrajs-cliversion references
RequestTracerclass - per-request timing viaperformance.now()withstart()/end()stage pairsMetricsStoresingleton - ring buffer for traces (200), build metrics (50), HMR events (100)Server-Timingheader output (W3C format for Chrome DevTools)- Tree-style terminal trace logs with bottleneck highlighting (yellow >50%, red >80%)
- Request tracing in dev server pipeline
- Conditional request tracing in production server (
trace.production:'off'|'header'|'on') - Gzip size estimates in build manifest
routeStats()- per-route avg/p50/p95/p99 response time aggregation- Trace API endpoints:
/_pyra/api/traces,/_pyra/api/traces/stats,/_pyra/api/traces/:id contributing.md
- Production server banner with timing and capability display
- Middleware and layout support in SSR pipeline - layouts nest outermost-to-innermost
- Middleware runner with
next()continuation pattern
- Updated
pyra startcommand to display production banner
- Prerendering and SSG support in build pipeline
CacheConfigandPrerenderConfigtypes- Prerendered pages served as static files from production server
- Dynamic route handling in production server
- SSG tests for prerendering
- API route handlers in dev server (
route.tswith HTTP method exports) - API route handlers in production server
- Catch-all route segments (
[...path]) in router trie catchAllparameter in route scanner- Route ID to URL conversion utility
- Router mismatch error in dev server
pyra doctorcommand - project diagnostics (SSR vs SPA mode detection, config validation, route scanning)- Dev server keyboard shortcuts (
rrestart,oopen browser,cclear,qquit,hhelp) - Dev banner - Vite-inspired startup display with route counts, SSR status, URLs
net-utils.ts- port finding, URL resolution, config helpers- Reporter utility with
withBanner()wrapper and silent mode support - Auto port detection - finds next available port if configured port is in use
- Terminal art for
pyradefault command
- Double help output in terminal
- Dev server returns structured
DevResulttype
- Build orchestrator producing
dist/client/+dist/server/+dist/manifest.json - Production server (
pyra start) serving prebuilt assets fromdist/ pyra startCLI command for production previewRouteManifestandManifestRouteEntrytypes
- Refactored build pipeline with client/server split
- File-system route scanner (
scanner.ts) - discoverspage.tsx,route.ts,layout.tsx,middleware.ts - Trie-based URL router (
router.ts) with priority: static > dynamic > catch-all - React SSR adapter (
pyrajs-adapter-react) implementingPyraAdapterinterface RequestContext- Web standardRequest,CookieJar, env vars, response helpers- Route-aware SSR in dev server via adapter pattern
PyraAdapterinterface for framework-agnostic rendering- Route groups
(name), dynamic segments[slug]support - Server-side
load()function support for data fetching - On-demand compilation via esbuild for route modules
- Dependency graph visualization (
pyra graph) - HTML, SVG, PNG, mermaid, dot, JSON formats - Performance banners in
initandcreatecommands
- Graph rendering issue
- CSS bundling issue
- Link error in local dev
- Monorepo setup with pnpm workspaces (shared, core, cli packages)
pyra dev- development server with HMR via WebSocketpyra build- production build via esbuildpyra init- project scaffolding with template selection- Config loader - auto-discovers
pyra.config.ts/.js/.mjs/.cjs/.pyrarc.* - Package manager detection (npm, pnpm, yarn, bun)
- Project templates for scaffolding
- Colored terminal output via picocolors