Skip to content

Latest commit

 

History

History
609 lines (478 loc) · 44.9 KB

File metadata and controls

609 lines (478 loc) · 44.9 KB

Changelog

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.

[0.30.10] - 2026-03-09

Fixed

  • All 5 example apps were unrunnable - every pyra.config.ts in examples/ was missing adapter: createReactAdapter(), causing pyra dev/build/start to fail immediately with a missing-adapter error; all configs now import and set the adapter correctly
  • Example package.json files 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 pendingBundles map in bundler.ts and a pendingServerCompiles map in dev-compiler.ts deduplicate in-flight compiles so late arrivals await the same Promise
  • Event loop blocking in static file handler - dev-static.ts servePublicFile() switched from fs.readFileSync to fs.promises.readFile; dev-server.ts CSS and static file handlers likewise converted to fs.promises.stat and fs.promises.readFile, unblocking concurrent requests during large file reads

Changed

  • <Link> used for all internal navigation in examples - all layout nav links and intra-app page links across pyra-blog, routing-showcase, ssg-cache, and middleware-auth now use <Link> from @pyra-js/adapter-react instead of raw <a href>, enabling client-side SPA navigation without full page reloads
  • Dead artifacts removed from examples/ - removed Kubo/ (orphaned dist artifact), basic-ts/ (pre-routing SPA era), test-config/ (internal test fixture), example.sh (empty file), and 6 stale pyra.config.*.ts files (basic, full, library, minimal, mode, react) that described a superseded config API; QUICK_START.md and USAGE.md also removed as they documented the same obsolete format
  • examples/pyra.config.reference.ts added — single accurate reference file documenting every real PyraConfig field with inline comments, including a defineConfigFn mode-aware example at the bottom

[0.30.6] - 2026-03-06

Added

  • pyraFramerMotion() plugin in @pyra-js/core - solves the Framer Motion + SSR incompatibility at the framework level
    • Implements the new headInjection plugin 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 their initial prop
  • <FramerMotionReady> component in @pyra-js/adapter-react, companion component for pyraFramerMotion(); place it in your root layout to remove the SSR override after React hydrates and Framer Motion's own useLayoutEffect has initialized, using requestAnimationFrame to ensure correct sequencing
  • headInjection?(): string hook on the PyraPlugin interface 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(), and useRouteError() now return safe defaults during server-side rendering instead of crashing with window is not defined; useLocation() returns { pathname: '/', search: '', hash: '', searchParams: new URLSearchParams() } on the server, useParams() returns {}, and useRouteError() returns null

[0.30.0] - 2026-03-06

Added

  • 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): Readable added to the PyraAdapter interface - optional; servers fall back to the existing buffered renderToHTML() path when not implemented
    • React adapter implements renderToStream() using React 18's renderToPipeableStream, piping output through a Node.js PassThrough stream
    • Dev server (dev-ssr.ts) detects adapter.renderToStream and takes the streaming path: sends rawBefore (everything up to <!--pyra-outlet-->) immediately, streams React output as chunks arrive, then appends hydration scripts and rawAfter on stream end
    • Production server takes the same streaming path when the adapter supports it
    • buildDeferredHeadScript() helper in dev-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 to document.head at 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() detects Accept-Encoding: gzip, compresses compressible content types, sets Content-Encoding: gzip and Vary: Accept-Encoding headers
    • compress option added to DevServerConfig (default true) - set to false to disable
    • sendResponse() utility in ProdServer - centralised helper replacing scattered sendWebResponse() call sites, handles both streaming and buffered responses and applies compression uniformly
  • Test suites across @pyra-js/core - new vitest suites covering previously untested subsystems
    • cors.test.ts - CORS header logic for allowed origins, credentials, preflight responses, and the origin: false disable path
    • render-mode.test.ts - resolveRouteRenderMode() covering SSR/SPA/SSG resolution from module exports and global config defaults
    • prod-assets.test.ts - production static file serving, cache header generation for hashed vs. non-hashed assets, and 404 handling
    • prod-compress.test.ts - gzip and identity response paths, Accept-Encoding negotiation, and compressible content-type detection
    • tracer.test.ts - RequestTracer start/end stage lifecycle, bottleneck detection thresholds, and Server-Timing header format
  • STABILITY document (STABILITY.md) - documents API stability guarantees and the pre-1.0 deprecation policy

Changed

  • prod-server.ts refactored into the src/prod/ directory for better code visibility - prod-assets.ts, prod-matcher.ts, prod-html.ts, and prod-server.ts; packages/core/src/prod-server.ts retained as a thin re-export shim so external callers require no changes
  • Streaming responses in ProdServer now use Readable.fromWeb() to pipe directly to the HTTP response without buffering the full body in memory first

[0.27.12] - 2026-03-05

Added

  • Browser-safe client entry point in @pyra-js/adapter-react - new client.ts exports only components and hooks that are safe to bundle for the browser; intentionally excludes createReactAdapter and fast-refresh-plugin which 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 package exports field
    • client.ts added as a named entry point in the tsup build config so it is emitted as client.js in dist/

Fixed

  • compileForServer in packages/core/src/dev/dev-compiler.ts now correctly externalizes React - previously React was being bundled into server compilation output instead of being treated as an external peer dependency
  • _require in fast-refresh-plugin.ts is 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.js createRequire call at import time
  • @babel/core and react-refresh/babel added to the external list in packages/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 external list in packages/core/src/bundler.ts as 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-refresh plugin from the mock adapter

[0.27.8] - 2026-03-04

Added

  • 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.ts in @pyra-js/adapter-react - esbuild onLoad plugin that runs react-refresh/babel via @babel/core on every .tsx/.jsx/.ts/.js file outside node_modules, inserting $RefreshReg$ and $RefreshSig$ registration calls; JSX and TypeScript transforms are still handled by esbuild after the plugin returns the modified source
    • getHMRPreamble?(): string added to the PyraAdapter interface 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_runtime endpoint in the dev server - lazily resolves react-refresh/runtime from the user's project root via createRequire, bundles it to ESM with esbuild once per session, and serves it with Cache-Control: no-cache; returns 404 if react-refresh is not installed
    • canFastRefresh(filePath, routesDir) helper in dev-hmr.ts - returns true for .tsx/.jsx/.ts/.js files that are not server-only route files (route.*, middleware.*); drives the update vs reload decision in the file watcher
    • window.__pyra_hmr_modules - a regular <script> tag (synchronous, runs before deferred module scripts) injected per page in dev-ssr.ts listing all client module URLs (/__pyra/modules/...) for the current page and its layouts; kept in sync after client-side navigations in the hydration script
    • window.__pyra_refresh — set by the RFR preamble to the react-refresh/runtime instance so the HMR client can call performReactRefresh() without importing the runtime again
    • @babel/core ^7.24.0 and react-refresh ^0.14.0 added as dependencies of @pyra-js/adapter-react
  • <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 during renderToString()
  • <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 its href matches the current pathname; drops in as a replacement for <Link> in nav bars and sidebars
  • Routing hooks and utilities in @pyra-js/adapter-react
    • useLocation() - returns the current URL as a URL object, reactive to client-side navigations
    • useRouter() - exposes navigate(href) and the current params object; thin wrapper around window.__pyra
    • Additional routing helpers exported from the package index
  • Deployment documentation (docs/deployment.md)

Changed

  • Adapter-agnostic core - DevServer, build(), and ProdServer no longer have any knowledge of React; the adapter must be supplied by the application via pyra.config.ts
    • @pyra-js/adapter-react removed from @pyra-js/cli dependencies - the CLI no longer bundles the React adapter; projects must install it separately (all templates already do)
    • Passing adapter: createReactAdapter() in pyra.config.ts is now the only way to opt into React SSR; omitting adapter is an error for pyra dev, pyra build, and pyra start (existing behaviour, now enforced without a React fallback)
  • HMR file watcher now distinguishes between two update strategies for the change event
    • .tsx/.jsx/.ts/.js files 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 add events 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 calls window.__pyra_refresh.performReactRefresh(); falls back to window.location.reload() if window.__pyra_refresh is not defined (non-React adapter) or if the re-import throws
  • bundleFile() in packages/core/src/bundler.ts accepts an optional extraPlugins: 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

Fixed

  • FormProps type 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 PyraAdapter interface resolved - corrected mismatched signatures that surfaced after the adapter-agnostic refactor

[0.25.2] - 2026-03-03

Added

  • 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 fakeReq via Object.create(req) ensures ctx.url reflects the navigated path so load() 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, calls window.__pyra.navigate() for client-side transitions
    • getHydrationScript() in the React adapter now generates a persistent PyraApp shell component using useState/useEffect instead of a one-shot hydrateRoot() call
      • useState(() => InitialComponent) + useState(initialData) hold the current page component and data
      • useEffect registers window.__pyra.navigate(href) and a popstate handler for Back/Forward button support
      • Layout chain comparison: if nav.layoutClientEntries differs from the boot-time pageLayouts, falls back to location.href (full reload) - keeps layout boundaries clean without complex diffing
      • window.scrollTo(0, 0) on each client-side navigation

Changed

  • 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

Fixed

  • Tailwind CSS scaffolding in create-pyra for full-stack projects now prepends @tailwind directives to the existing style.css instead of creating a separate index.css - preserves all custom template styles (nav, cards, hero, etc.) that were previously lost
  • Tailwind index.css was previously written to src/ but full-stack layouts import from src/routes/, file is no longer created separately; directives are merged into the existing style.css in src/routes/

[0.24.0] - 2026-03-03

Added

  • Router and React Compiler options in create-pyra wizard, new post-copy patching system in packages/create-pyra/src/patches.ts applies 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.tsx with createBrowserRouter, converts App.tsx into a layout shell with <Outlet />, and creates src/pages/Home.tsx
    • TanStack Router patch: rewrites main.tsx with a fully code-based router, removes App.tsx (root route replaces it)
    • React (both SSR and SPA) now prompts to enable the React Compiler, adds babel-plugin-react-compiler and esbuild-plugin-babel devDependencies and rewrites pyra.config.ts/js with a working Pyra plugin block
  • 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 old pyrajs-* package names that re-export from @pyra-js/* so existing projects continue to work without code changes
    • pyrajs-shared → re-exports from @pyra-js/shared
    • pyrajs-core → re-exports from @pyra-js/core
    • pyrajs-adapter-react → re-exports from @pyra-js/adapter-react
    • pyrajs-cli → re-exports from @pyra-js/cli; includes a bin/pyra.mjs shim so the pyra binary continues to resolve
    • Each compat package ships a README.md with a deprecation notice and a migration guide

Changed

  • All four packages renamed to the @pyra-js npm scope
    • pyrajs-shared@pyra-js/shared
    • pyrajs-core@pyra-js/core
    • pyrajs-adapter-react@pyra-js/adapter-react
    • pyrajs-cli@pyra-js/cli
  • defineConfig and 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/cli and never from @pyra-js/shared directly
  • All generated project templates (create-pyra and packages/cli) updated to import defineConfig from @pyra-js/cli

Fixed

  • babel-plugin-react-compiler version 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

[0.23.1] - 2026-02-28

Added

  • Dev server proxy middleware - configure upstream request forwarding via server.proxy in pyra.config.ts
    • New packages/core/src/dev/dev-proxy.ts with matchProxyRule() and handleProxyRequest() - zero new dependencies, uses Node's built-in http/https modules
    • Config shape: proxy: { '/api': 'http://localhost:4000' } (string shorthand) or { target, changeOrigin?, rewrite? } (object form)
    • changeOrigin: true rewrites the Host header to the upstream host (needed for virtual-host upstreams)
    • rewrite applies 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 Gateway with a plain-text body on upstream connection errors

Improved

  • pyra graph HTML 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 (k 70→35) and node radii (internal 7→5, external 5→3) for a denser, more readable layout

Removed

  • Dead features field removed from PyraConfig in packages/shared/src/types.ts - features.cssModules, features.typeCheck, and features.jsx were declared but never read by any package
  • Dead esbuild field removed from PyraConfig in packages/shared/src/types.ts - was typed as Record<string, any> and never consumed; esbuild options are passed through the build system internally

Fixed

  • vitest.workspace.ts reverted to a plain array export - defineWorkspace was 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 in pyrajs-shared/package.json; the remaining test coverage for the image plugin is provided by the integration tests

[0.21.23] - 2026-02-27

Fixed

  • pyra doctor no 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 file
  • pyra doctor TypeScript check no longer triggers Node.js DEP0190 deprecation warning ("Passing args to a child process with shell option true"), runTscCheck() now spawns cmd.exe /c tsc.cmd --noEmit on Windows instead of using shell: true, eliminating both the warning and the follow-on EINVAL spawn error

Security

  • 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" to pnpm.overrides in the root package.json

Removed

  • Deprecated framework field removed from PyraConfig in packages/shared/src/types.ts - the adapter field fully replaces it and has been the intended API since v0.3

Changed

  • build.splitting is now read from config.build?.splitting instead of being hardcoded to true, applies to both the SSR client build (build-orchestrator.ts) and the SPA build (buildSPA.ts); server build remains hardcoded to false as Node.js ESM cannot load split chunks dynamically

Refactored

  • packages/cli/src/bin.ts extracted into per-command files under packages/cli/src/commands/
    • commands/dev.ts - exports DevOptions and devCommand()
    • commands/build.ts - exports BuildOptions and buildCommand()
    • commands/start.ts - exports StartOptions and startCommand()
    • commands/init.ts - exports InitOptions and initCommand()
    • bin.ts reduced from ~625 lines to ~155 lines; now a pure wiring file that resolves silent/color and delegates to each command function
    • Follows the same pattern already used by commands/graph.ts and commands/doctor.ts
  • packages/core/src/build.ts split into focused modules under packages/core/src/build/
    • build-utils.ts - routeIdToSafeName(), getAncestorDirIds() pure string utilities
    • build-client.ts - buildClientOutputMap(), buildClientLayoutOutputMap() esbuild metafile correlation
    • build-server.ts - buildServerOutputMap(), buildServerMwLayoutOutputMap() server entry path resolution
    • build-manifest.ts - assembleManifest(), buildEmptyManifest(), getMimeType(), DEFAULT_SHELL
    • build-prerender.ts - buildPrerenderAssetTags() SSG asset tag generation
    • build-report.ts - printBuildReport() and private helpers (estimateGzipSize, getSharedChunks, formatSize, countFilesRecursive)
    • build-orchestrator.ts - main build() function
    • build.ts replaced with a 3-line re-export shim; all external imports remain unchanged
  • packages/core/src/prod-server.ts split into focused modules under packages/core/src/prod/
    • prod-matcher.ts - MatchResult, buildMatcher() trie-based manifest route matcher
    • prod-assets.ts - buildCacheControlHeader(), getCacheControl(), getContentType(), buildAssetTags() static asset helpers
    • prod-html.ts - DEFAULT_SHELL, getErrorHTML(), get404HTML() HTML string generators
    • prod-server.ts - ProdServer class importing from the three helpers above
    • prod-server.ts at src/ root replaced with a 4-line re-export shim; all external imports remain unchanged

[0.19.0] - 2026-02-26

Added

  • CORS support - configure cross-origin resource sharing via the cors field in pyra.config.ts
    • New CorsConfig type in packages/shared/src/types.ts: origin, methods, allowedHeaders, exposedHeaders, credentials, maxAge fields
    • packages/core/src/cors.tsbuildCORSHeaders() and applyCors() helpers; origin resolution supports wildcard, string, and array allow-lists; automatic OPTIONS preflight handling
    • Dev server and production server both apply CORS headers via applyCors() before routing
  • 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

Refactored

  • packages/core/src/dev-server.ts split into eight focused modules under packages/core/src/dev/
    • dev-compiler.ts — on-demand esbuild compilation
    • dev-hmr.ts — WebSocket HMR server and client injection
    • dev-dashboard.ts/_pyra dashboard and API endpoint handlers
    • dev-static.ts — static file and public/ directory serving
    • dev-api.ts — API route dispatch and method validation
    • dev-routes.ts — page route matching and SSR pipeline entry
    • dev-errors.ts — error boundary rendering and 404 handling
    • dev-ssr.ts — core SSR assembly (load → render → shell → inject assets)
    • Dead code removed; dev-server.ts now re-exports and wires the modules together

Fixed

  • TypeScript typecheck errors in Image.test.tsx — corrected mockContext, 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

[0.18.4] - 2026-02-22

Added

  • Module resolution and path alias support via ResolveConfig is now wired to esbuild in both dev and production builds
    • bundler.ts: new buildEsbuildResolveOptions() helper translates ResolveConfig to esbuild alias, resolveExtensions, and mainFields options; alias values are resolved to absolute paths automatically
    • dev-server.ts: all four bundleFile() call sites now pass config.resolve
    • build.ts: buildEsbuildResolveOptions() spread into both client and server esbuild.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 the route-match stage after the router returns
  • MetricsStore.isActiveBuild() method - returns true when startBuild() has been called without a matching finishBuild(); used to coordinate build lifecycle across the file watcher and request handler
  • Build metrics now fully wired in dev server
    • startBuild() called in the change and add watcher handlers when a source file is saved
    • Orphaned builds (two rapid saves with no intervening request) are closed before the next startBuild() via isActiveBuild()
    • finishBuild() called after the response is sent for the first routed request following a file change; totalDuration spans the full file-save-to-response cycle
  • Dashboard "Recent Requests" section - live table polling /_pyra/api/traces every 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

Fixed

  • Double route-match entry in request traces - the dev server was opening two separate spans for route matching; the second (zero-duration) span is now replaced with tracer.setDetail(), attaching the route ID to the first span and keeping extractRouteId() correct
  • Dashboard empty state messages used em dashes; replaced with commas for consistency

Changed

  • Dashboard stat cards show -- instead of 0 when 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

[0.16.1] - 2026-02-20

Added

  • CSS pipeline fix in dev server - import './style.css' in layout/page files now works without Flash of Unstyled Content

    • bundler.ts: new cssOutputCache stores CSS extracted by esbuild separately from the JS bundle; removed the old document.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 proper text/css responses
    • handlePageRouteInner eagerly calls bundleFile() for each layout + page before assembly, then injects <link rel="stylesheet" href="/__pyra/styles/..."> tags into <head> via <!--pyra-head-->
    • style.css moved back to src/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 - excludes packages/cli/templates/** from Tailwind CSS extension scanning, preventing false "unable to load config" errors caused by the extension attempting CommonJS require() on ESM-only template config files

Changed

  • create-pyra rendering mode prompt labels updated from "SSR / SPA" to "Full-stack / Frontend (SPA)" - value fields ("ssr" / "spa") are unchanged so template selection is unaffected
  • packages/cli dev:link script changed from pnpm link -g to npm link - now consistent with create-pyra's dev:link, making npm link pyrajs-cli work correctly in generated test projects
  • Full-stack template CSS redesigned across all six templates (create-pyra React/Preact TS/JS and pyrajs-cli React TS/JS fullstack) to match the Pyra visual identity

Fixed

  • vitest.workspace.ts: removed defineWorkspace import that broke TypeScript in Vitest 4 (the export was removed in Vitest 4; workspace files now export the array directly)

[0.15.2] - 2026-02-19

Added

  • Image optimization plugin (pyraImages()) - framework-agnostic, lives entirely in packages/core
    • Activate via plugins: [pyraImages()] in pyra.config.ts
    • sharp is 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: images key maps source paths to ImageManifestEntry with all variant metadata
    • Prod server: /_pyra/image reads manifest and serves pre-built files with immutable cache headers
    • Config options: formats (default ['webp']), sizes (default [640, 1280, 1920]), quality (default 80), cacheDir
    • Plugin hooks buildStart and buildEnd are now wired in build.ts (previously defined but never called)
  • 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, style props
  • public/ directory handling — static assets now served transparently at the root URL (like Vite)
    • Dev server: resolvePublicFilePath() checks public/ before route matching; files served with Cache-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 to dist/client/ at the end of the build
  • SPA rendering mode option in pyra init wizard — after selecting React, users are now asked whether the project is Full-stack (SSR) or Frontend (SPA)
    • Full-stack → react-ts-fullstack / react-js-fullstack template (file-based routing, server required)
    • SPA → react-ts-spa / react-js-spa template (entry: config, no server required)
  • New CLI templates: react-ts-spa and react-js-spa
    • pyra.config with entry: pointing to src/main.tsx / src/main.jsx
    • src/App.tsx / src/App.jsx with minimal starter component
    • public/favicon.svg included
  • public/favicon.svg added to all CLI templates (vanilla-ts, vanilla-js, react-ts-fullstack, react-js-fullstack, react-ts-spa, react-js-spa)
  • public/favicon.svg added to all create-pyra templates (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 to index.html in all SPA/vanilla templates (both packages/cli and packages/create-pyra)
  • Favicon <link> tag added to DEFAULT_SHELL in packages/adapter-react — full-stack (SSR) projects now include the favicon in the generated HTML shell

Changed

  • PyraPlugin.buildEnd signature updated: now receives { manifest, outDir, root } context object instead of no arguments

[0.13.4] - 2026-02-18

Added

  • SPA production build pipeline - pyra build now produces static output for entry-based projects
    • Detected when config.entry is a string (e.g. entry: 'src/main.tsx') with no file-based routing
    • index.html transformed at build time: dev-time <script type="module" src="..."> tags removed, hashed script and CSS injected
    • public/ directory copied to dist/ 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

Changed

  • spawnPM() in create-pyra now uses cmd.exe /c <pm> install on Windows instead of shell: true with .cmd extension - fixes dependency installation failing silently after the DEP0190 fix in 0.13.0

Fixed

  • Node.js DEP0190 deprecation warning (Passing args to a child process with shell option true) no longer appears when running create-pyra
    • commandExists() changed to shell: false - where.exe / which are real executables
    • spawnPM() changed to shell: false with cmd.exe /c on Windows, plain spawn on Unix

Refactored

  • packages/core/src/build.ts split into focused modules
    • build.ts - SSR/SSG orchestrator only
    • buildSPA.ts - SPA static build (self-contained with its own helpers)
    • types.ts - BuildOrchestratorOptions and BuildResult interfaces (re-exported from index.ts)

[0.13.0] - 2026-02-18

Added

  • 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 in pyrajs-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:link and dev:unlink scripts to create-pyra/package.json for 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 route
    • react-js-fullstack/ - JavaScript fullstack SSR template (same structure, no TypeScript)
    • vanilla-ts/ - Minimal vanilla TypeScript SPA template
    • vanilla-js/ - Minimal vanilla JavaScript SPA template

Changed

  • Tailwind prompt in create-pyra wizard 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 TailwindPreset type and generateShadcnTailwindConfig() helper
    • Summary row now shows "Yes" / "No" instead of preset name

Fixed

  • dev:link in create-pyra now uses npm link instead of pnpm link --global - avoids ERR_PNPM_NO_GLOBAL_BIN_DIR error when pnpm global bin dir is not configured

[0.12.2] - 2026-02-16

Changed

  • 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)

[0.12.1] - 2025-02-16

Added

  • Install dependencies prompt in create-pyra wizard, 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 via npm create)

Fixed

  • create-pyra no longer crashes with EBADF when launched through npm create on Windows/Git Bash

[0.9.5] - 2025-02-16

Changed

  • Replaced @inquirer/prompts with @clack/prompts in create-pyra for a cleaner interactive wizard experience
  • Updated architecture documentation

Added

  • theme.ts in create-pyra - color palette constants and styled progress indicators
  • tree.ts in create-pyra - file tree formatter for post-scaffold output

Fixed

  • Corrected prompt flow issue after migrating to clack

[0.9.4] - 2025

Added

  • Render mode system (ssr, spa, ssg) configurable globally or per-route
  • render-mode.ts in 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-pyra wizard
  • Preact framework support - preact-ts, preact-js, preact-spa-ts, preact-spa-js templates
  • Project templates for create-pyra: react-ts, react-js, vanilla-ts, vanilla-js

Changed

  • Updated create-pyra templates to account for SSR/SPA/SSG rendering modes
  • Updated types to include new rendering engine types
  • Refactored pyra init command and scaffold system
  • Exported renderMode via config loading

Fixed

  • Corrected replaceAll error inside create-pyra

[0.9.3] - 2025

Added

  • Comprehensive documentation: middleware, SSR, layouts, API routes, CLI, adapters, request tracing
  • create-pyra readme

Changed

  • Updated package versioning across all packages (pyrajs-cli 0.10.1, create-pyra 0.11.0)

[0.9.2] - 2025

Added

  • create-pyra standalone package - interactive project scaffolding via npm create pyra
  • GitHub Actions CI workflows
  • Publish script for package releases
  • Base tsconfig.json at project root with project references
  • Pyra logo and brand colors in create-pyra wizard
  • Example projects in examples/ folder

Fixed

  • Rebuilt types to align with new root tsconfig
  • npm strict publish issue resolved via bin wrapper

[0.9.1] - 2025

Added

  • Error boundary types (ErrorPageProps, ErrorModule)
  • error.tsx and 404.tsx route 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 inflightCount tracking and 10-second drain timeout

Changed

  • Improved terminal styling - Pyra text in red via chalk, cleaner default action display
  • Dynamic versioning for CLI template dependencies

Fixed

  • Templates now use dynamic versioning for pyrajs-cli version references

[0.9.0] - 2025

Added

  • RequestTracer class - per-request timing via performance.now() with start()/end() stage pairs
  • MetricsStore singleton - ring buffer for traces (200), build metrics (50), HMR events (100)
  • Server-Timing header 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

[0.8.0] - 2025

Added

  • 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

Changed

  • Updated pyra start command to display production banner

[0.7.0] - 2025

Added

  • Prerendering and SSG support in build pipeline
  • CacheConfig and PrerenderConfig types
  • Prerendered pages served as static files from production server
  • Dynamic route handling in production server
  • SSG tests for prerendering

[0.6.0] - 2025

Added

  • API route handlers in dev server (route.ts with HTTP method exports)
  • API route handlers in production server
  • Catch-all route segments ([...path]) in router trie
  • catchAll parameter in route scanner
  • Route ID to URL conversion utility

Fixed

  • Router mismatch error in dev server

[0.5.0] - 2025

Added

  • pyra doctor command - project diagnostics (SSR vs SPA mode detection, config validation, route scanning)
  • Dev server keyboard shortcuts (r restart, o open browser, c clear, q quit, h help)
  • 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 pyra default command

Fixed

  • Double help output in terminal

Changed

  • Dev server returns structured DevResult type

[0.4.0] - 2025

Added

  • Build orchestrator producing dist/client/ + dist/server/ + dist/manifest.json
  • Production server (pyra start) serving prebuilt assets from dist/
  • pyra start CLI command for production preview
  • RouteManifest and ManifestRouteEntry types

Changed

  • Refactored build pipeline with client/server split

[0.3.0] - 2025

Added

  • File-system route scanner (scanner.ts) - discovers page.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) implementing PyraAdapter interface
  • RequestContext - Web standard Request, CookieJar, env vars, response helpers
  • Route-aware SSR in dev server via adapter pattern
  • PyraAdapter interface 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

[0.2.0] - 2025

Added

  • Dependency graph visualization (pyra graph) - HTML, SVG, PNG, mermaid, dot, JSON formats
  • Performance banners in init and create commands

Fixed

  • Graph rendering issue
  • CSS bundling issue
  • Link error in local dev

[0.1.0] - 2025

Added

  • Monorepo setup with pnpm workspaces (shared, core, cli packages)
  • pyra dev - development server with HMR via WebSocket
  • pyra build - production build via esbuild
  • pyra 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