litestar-vite-plugin 0.22.1 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -45,6 +45,12 @@ interface AstroConfigPartial {
45
45
  port?: number;
46
46
  strictPort?: boolean;
47
47
  proxy?: Record<string, unknown>;
48
+ hmr?: {
49
+ protocol?: "ws" | "wss";
50
+ host?: string;
51
+ clientPort?: number;
52
+ path?: string;
53
+ };
48
54
  };
49
55
  };
50
56
  }
package/dist/js/astro.js CHANGED
@@ -2,7 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { readBridgeConfig } from "./shared/bridge-schema.js";
4
4
  import { DEBOUNCE_MS } from "./shared/constants.js";
5
- import { normalizeHost, resolveHotFilePath } from "./shared/network.js";
5
+ import { normalizeHost, resolveHotFilePath, resolveLitestarPort } from "./shared/network.js";
6
6
  import { createLitestarTypeGenPlugin } from "./shared/typegen-plugin.js";
7
7
  function resolveConfig(config = {}) {
8
8
  let hotFile;
@@ -19,6 +19,8 @@ function resolveConfig(config = {}) {
19
19
  }
20
20
  }
21
21
  const runtime = readBridgeConfig();
22
+ let assetUrl;
23
+ let litestarPort;
22
24
  if (runtime) {
23
25
  hasPythonConfig = true;
24
26
  const hot = runtime.hotFile;
@@ -26,10 +28,15 @@ function resolveConfig(config = {}) {
26
28
  proxyMode = runtime.proxyMode;
27
29
  port = runtime.port;
28
30
  pythonExecutor = runtime.executor;
31
+ assetUrl = runtime.assetUrl;
29
32
  if (runtime.types) {
30
33
  pythonTypesConfig = runtime.types;
31
34
  }
32
35
  }
36
+ const resolvedLitestarPort = resolveLitestarPort(runtime?.litestarPort, runtime?.appUrl);
37
+ if (resolvedLitestarPort !== null) {
38
+ litestarPort = resolvedLitestarPort;
39
+ }
33
40
  let typesConfig = false;
34
41
  const defaultTypesOutput = "src/generated";
35
42
  const buildTypeDefaults = (output) => ({
@@ -106,6 +113,8 @@ function resolveConfig(config = {}) {
106
113
  hotFile,
107
114
  proxyMode,
108
115
  port,
116
+ litestarPort,
117
+ assetUrl,
109
118
  executor: pythonExecutor,
110
119
  hasPythonConfig
111
120
  };
@@ -114,6 +123,7 @@ function createProxyPlugin(config) {
114
123
  return {
115
124
  name: "litestar-astro-proxy",
116
125
  config() {
126
+ const hmrPath = `${(config.assetUrl ?? "/static").replace(/\/$/, "")}/vite-hmr`;
117
127
  return {
118
128
  server: {
119
129
  // Force IPv4 binding for consistency with Python proxy configuration
@@ -125,6 +135,15 @@ function createProxyPlugin(config) {
125
135
  port: config.port,
126
136
  strictPort: true
127
137
  } : {},
138
+ // Route HMR through the Litestar port so DevTools never sees the framework port.
139
+ ...config.litestarPort !== void 0 ? {
140
+ hmr: {
141
+ protocol: "ws",
142
+ host: "127.0.0.1",
143
+ clientPort: config.litestarPort,
144
+ path: hmrPath
145
+ }
146
+ } : {},
128
147
  proxy: {
129
148
  [config.apiPrefix]: {
130
149
  target: config.apiProxy,
@@ -4,130 +4,121 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Litestar Vite Dev Server</title>
7
- <style rel="stylesheet" crossorigin>/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */
8
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-blue-500:oklch(62.3% .214 259.815);--color-purple-500:oklch(62.7% .265 303.9);--color-gray-900:oklch(21% .034 264.665);--color-white:#fff;--spacing:.25rem;--container-2xl:42rem;--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--leading-relaxed:1.625;--radius-2xl:1rem;--radius-3xl:1.5rem;--drop-shadow-lg:0 4px 4px #00000026;--animate-ping:ping 1s cubic-bezier(0, 0, .2, 1) infinite;--blur-lg:16px;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-brand-primary:oklch(22% .03 265);--color-brand-accent:oklch(78% .16 85);--color-brand-accent-light:oklch(85% .12 85);--color-brand-gray:oklch(90% .01 265);--color-brand-success:oklch(65% .2 145);--color-brand-dots:oklch(26% .03 265);--animate-blob:blob 7s infinite ease-in-out}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.relative{position:relative}.inset-0{inset:calc(var(--spacing) * 0)}.top-0{top:calc(var(--spacing) * 0)}.-right-4{right:calc(var(--spacing) * -4)}.-bottom-8{bottom:calc(var(--spacing) * -8)}.-left-4{left:calc(var(--spacing) * -4)}.left-20{left:calc(var(--spacing) * 20)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-12{margin-top:calc(var(--spacing) * 12)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-10{margin-bottom:calc(var(--spacing) * 10)}.ml-2{margin-left:calc(var(--spacing) * 2)}.flex{display:flex}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.h-1{height:calc(var(--spacing) * 1)}.h-3{height:calc(var(--spacing) * 3)}.h-4{height:calc(var(--spacing) * 4)}.h-6{height:calc(var(--spacing) * 6)}.h-16{height:calc(var(--spacing) * 16)}.h-20{height:calc(var(--spacing) * 20)}.h-72{height:calc(var(--spacing) * 72)}.h-full{height:100%}.h-px{height:1px}.min-h-screen{min-height:100vh}.w-3{width:calc(var(--spacing) * 3)}.w-4{width:calc(var(--spacing) * 4)}.w-6{width:calc(var(--spacing) * 6)}.w-72{width:calc(var(--spacing) * 72)}.w-auto{width:auto}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.animate-blob{animation:var(--animate-blob);animation:var(--animate-blob)}.animate-ping{animation:var(--animate-ping)}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)))}.overflow-hidden{overflow:hidden}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-3xl{border-radius:var(--radius-3xl)}.rounded-full{border-radius:3.40282e38px}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.border-white\/10{border-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.bg-blue-500\/10{background-color:#3080ff1a}@supports (color:color-mix(in lab, red, red)){.bg-blue-500\/10{background-color:color-mix(in oklab, var(--color-blue-500) 10%, transparent)}}.bg-brand-accent\/10{background-color:#e6ad001a}@supports (color:color-mix(in lab, red, red)){.bg-brand-accent\/10{background-color:color-mix(in oklab, var(--color-brand-accent) 10%, transparent)}}.bg-brand-accent\/20{background-color:#e6ad0033}@supports (color:color-mix(in lab, red, red)){.bg-brand-accent\/20{background-color:color-mix(in oklab, var(--color-brand-accent) 20%, transparent)}}.bg-brand-accent\/30{background-color:#e6ad004d}@supports (color:color-mix(in lab, red, red)){.bg-brand-accent\/30{background-color:color-mix(in oklab, var(--color-brand-accent) 30%, transparent)}}.bg-brand-success{background-color:var(--color-brand-success)}.bg-gray-900\/60{background-color:#10182899}@supports (color:color-mix(in lab, red, red)){.bg-gray-900\/60{background-color:color-mix(in oklab, var(--color-gray-900) 60%, transparent)}}.bg-purple-500\/10{background-color:#ac4bff1a}@supports (color:color-mix(in lab, red, red)){.bg-purple-500\/10{background-color:color-mix(in oklab, var(--color-purple-500) 10%, transparent)}}.bg-purple-500\/20{background-color:#ac4bff33}@supports (color:color-mix(in lab, red, red)){.bg-purple-500\/20{background-color:color-mix(in oklab, var(--color-purple-500) 20%, transparent)}}.bg-white\/5{background-color:#ffffff0d}@supports (color:color-mix(in lab, red, red)){.bg-white\/5{background-color:color-mix(in oklab, var(--color-white) 5%, transparent)}}.bg-linear-to-r{--tw-gradient-position:to right}@supports (background-image:linear-gradient(in lab, red, red)){.bg-linear-to-r{--tw-gradient-position:to right in oklab}}.bg-linear-to-r{background-image:linear-gradient(var(--tw-gradient-stops))}.from-brand-accent{--tw-gradient-from:var(--color-brand-accent);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-transparent{--tw-gradient-from:transparent;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.via-brand-accent{--tw-gradient-via:var(--color-brand-accent);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.to-brand-accent-light{--tw-gradient-to:var(--color-brand-accent-light);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-transparent{--tw-gradient-to:transparent;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.bg-clip-text{-webkit-background-clip:text;background-clip:text}.p-6{padding:calc(var(--spacing) * 6)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-8{padding-inline:calc(var(--spacing) * 8)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-10{padding-block:calc(var(--spacing) * 10)}.py-12{padding-block:calc(var(--spacing) * 12)}.pt-8{padding-top:calc(var(--spacing) * 8)}.text-center{text-align:center}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.text-brand-accent{color:var(--color-brand-accent)}.text-brand-gray{color:var(--color-brand-gray)}.text-brand-gray\/30{color:#dbdee54d}@supports (color:color-mix(in lab, red, red)){.text-brand-gray\/30{color:color-mix(in oklab, var(--color-brand-gray) 30%, transparent)}}.text-brand-gray\/60{color:#dbdee599}@supports (color:color-mix(in lab, red, red)){.text-brand-gray\/60{color:color-mix(in oklab, var(--color-brand-gray) 60%, transparent)}}.text-brand-gray\/80{color:#dbdee5cc}@supports (color:color-mix(in lab, red, red)){.text-brand-gray\/80{color:color-mix(in oklab, var(--color-brand-gray) 80%, transparent)}}.text-brand-success{color:var(--color-brand-success)}.text-transparent{color:#0000}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-70{opacity:.7}.opacity-75{opacity:.75}.mix-blend-multiply{mix-blend-mode:multiply}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-inner{--tw-shadow:inset 0 2px 4px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.blur-xl{--tw-blur:blur(var(--blur-xl));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.drop-shadow-lg{--tw-drop-shadow-size:drop-shadow(0 4px 4px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--drop-shadow-lg));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur-lg{--tw-backdrop-blur:blur(var(--blur-lg));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-300{--tw-duration:.3s;transition-duration:.3s}.animation-delay-2000{animation-delay:2s}.animation-delay-4000{animation-delay:4s}@media (hover:hover){.group-hover\:translate-x-1:is(:where(.group):hover *){--tw-translate-x:calc(var(--spacing) * 1);translate:var(--tw-translate-x) var(--tw-translate-y)}.group-hover\:bg-brand-accent:is(:where(.group):hover *){background-color:var(--color-brand-accent)}.group-hover\:text-brand-accent-light:is(:where(.group):hover *){color:var(--color-brand-accent-light)}.group-hover\:text-white:is(:where(.group):hover *){color:var(--color-white)}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.hover\:scale-110:hover{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:border-brand-accent\/50:hover{border-color:#e6ad0080}@supports (color:color-mix(in lab, red, red)){.hover\:border-brand-accent\/50:hover{border-color:color-mix(in oklab, var(--color-brand-accent) 50%, transparent)}}.hover\:bg-white\/10:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/10:hover{background-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}}@media (width>=40rem){.sm\:px-12{padding-inline:calc(var(--spacing) * 12)}.sm\:py-12{padding-block:calc(var(--spacing) * 12)}.sm\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}}}@keyframes blob{0%,to{transform:translate(0)scale(1)}33%{transform:translate(30px,-50px)scale(1.1)}66%{transform:translate(-20px,20px)scale(.9)}}body{background-color:var(--color-brand-primary);background-image:radial-gradient(var(--color-brand-dots) 1px, transparent 1px);color:var(--color-brand-gray);background-size:24px 24px}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap">
10
+ <style rel="stylesheet" crossorigin>/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
11
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-duration:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0}}}@layer theme{:root,:host{--font-sans:"Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--font-mono:"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;--spacing:.25rem;--container-lg:32rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--font-weight-light:300;--font-weight-medium:500;--font-weight-semibold:600;--tracking-widest:.1em;--leading-relaxed:1.625;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-navy:#202235;--color-navy-surface:#2a2d40;--color-navy-surface-variant:#1a1c2e;--color-gold:#edb641;--color-gold-light:#ffd480;--color-on-surface:#f8fafc;--color-on-surface-muted:#cbd5e1;--color-on-surface-subtle:#94a3b8;--color-outline:#334155;--color-success:#4ade80;--radius-brand:8px;--radius-brand-lg:12px;--shadow-text-glow-dark:0 0 24px #edb64138}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.relative{position:relative}.mt-8{margin-top:calc(var(--spacing) * 8)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-10{margin-bottom:calc(var(--spacing) * 10)}.mb-12{margin-bottom:calc(var(--spacing) * 12)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.inline{display:inline}.inline-flex{display:inline-flex}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-12{height:calc(var(--spacing) * 12)}.h-14{height:calc(var(--spacing) * 14)}.h-full{height:100%}.min-h-screen{min-height:100vh}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-auto{width:auto}.w-full{width:100%}.max-w-lg{max-width:var(--container-lg)}.min-w-0{min-width:calc(var(--spacing) * 0)}.flex-1{flex:1}.flex-none{flex:none}.animate-pulse-dot{animation:2s ease-in-out infinite pulse-dot}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-6{gap:calc(var(--spacing) * 6)}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}.rounded-brand{border-radius:var(--radius-brand)}.rounded-brand-lg{border-radius:var(--radius-brand-lg)}.rounded-full{border-radius:3.40282e38px}.border{border-style:var(--tw-border-style);border-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-\[3px\]{border-left-style:var(--tw-border-style);border-left-width:3px}.border-outline{border-color:var(--color-outline)}.border-l-gold{border-left-color:var(--color-gold)}.bg-navy-surface{background-color:var(--color-navy-surface)}.bg-navy-surface-variant{background-color:var(--color-navy-surface-variant)}.bg-success{background-color:var(--color-success)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-8{padding-inline:calc(var(--spacing) * 8)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-16{padding-block:calc(var(--spacing) * 16)}.text-center{text-align:center}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-all{word-break:break-all}.text-gold{color:var(--color-gold)}.text-on-surface{color:var(--color-on-surface)}.text-on-surface-muted{color:var(--color-on-surface-muted)}.text-on-surface-subtle{color:var(--color-on-surface-subtle)}.uppercase{text-transform:uppercase}.opacity-60{opacity:.6}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.select-all{-webkit-user-select:all;user-select:all}.text-brand-glow{text-shadow:var(--shadow-text-glow-dark)}@media (hover:hover){.group-hover\:translate-x-0\.5:is(:where(.group):hover *){--tw-translate-x:calc(var(--spacing) * .5);translate:var(--tw-translate-x) var(--tw-translate-y)}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.hover\:bg-navy:hover{background-color:var(--color-navy)}.hover\:text-gold:hover{color:var(--color-gold)}.hover\:text-gold-light:hover{color:var(--color-gold-light)}.hover\:opacity-80:hover{opacity:.8}}@media (width>=40rem){.sm\:px-10{padding-inline:calc(var(--spacing) * 10)}.sm\:py-10{padding-block:calc(var(--spacing) * 10)}.sm\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}}}html{font-family:var(--font-sans);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{background-color:var(--color-navy);color:var(--color-on-surface-muted);background-image:radial-gradient(#f8fafc0a 1px,#0000 1px);background-size:24px 24px}@keyframes pulse-dot{0%,to{opacity:1;transform:scale(1)}50%{opacity:.55;transform:scale(.92)}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}
9
12
  /*$vite$:1*/</style>
10
13
  </head>
11
- <body class="antialiased min-h-screen flex flex-col items-center justify-center overflow-hidden">
14
+ <body class="min-h-screen flex items-center justify-center px-6 py-16">
12
15
 
13
- <!-- Main Container -->
14
- <div class="relative w-full max-w-2xl px-6 py-12">
16
+ <main class="w-full max-w-lg">
15
17
 
16
- <!-- Animated Background Blobs -->
17
- <div class="absolute top-0 -left-4 w-72 h-72 bg-brand-accent/10 rounded-full mix-blend-multiply filter blur-xl opacity-70 animate-blob"></div>
18
- <div class="absolute top-0 -right-4 w-72 h-72 bg-purple-500/10 rounded-full mix-blend-multiply filter blur-xl opacity-70 animate-blob animation-delay-2000"></div>
19
- <div class="absolute -bottom-8 left-20 w-72 h-72 bg-blue-500/10 rounded-full mix-blend-multiply filter blur-xl opacity-70 animate-blob animation-delay-4000"></div>
18
+ <!-- Brand hero: signature display-lg treatment per DESIGN.md -->
19
+ <header class="text-center mb-12">
20
+ <div class="flex items-center justify-center gap-6 mb-10">
21
+ <a href="https://litestar.dev" target="_blank" rel="noreferrer" class="block transition-opacity duration-200 hover:opacity-80" aria-label="Litestar">
22
+ <svg viewBox="0 52 410 250" xmlns="http://www.w3.org/2000/svg" class="h-14 w-auto">
23
+ <defs>
24
+ <clipPath id="9eb7762d41">
25
+ <path d="M 15.933594 105 L 328 105 L 328 259 L 15.933594 259 Z M 15.933594 105 " clip-rule="nonzero"/>
26
+ </clipPath>
27
+ <clipPath id="183d3cc178">
28
+ <path d="M 142 78.769531 L 359.433594 78.769531 L 359.433594 296.269531 L 142 296.269531 Z M 142 78.769531 " clip-rule="nonzero"/>
29
+ </clipPath>
30
+ </defs>
31
+ <g clip-path="url(#9eb7762d41)">
32
+ <path fill="#edb641" d="M 147.625 240.3125 C 161.5 233.984375 173.554688 227.011719 183.425781 220.550781 C 202.304688 208.203125 226.4375 185.242188 227.761719 183.410156 L 218.917969 177.503906 L 211.257812 172.386719 L 235.503906 171.441406 L 243.296875 171.136719 L 245.414062 163.640625 L 252.007812 140.304688 L 260.402344 163.054688 L 263.097656 170.363281 L 270.890625 170.058594 L 295.136719 169.113281 L 276.078125 184.117188 L 269.953125 188.9375 L 272.652344 196.25 L 281.046875 218.996094 L 260.871094 205.523438 L 254.390625 201.195312 L 248.265625 206.015625 L 229.207031 221.023438 L 232.480469 209.425781 L 235.796875 197.691406 L 236.207031 196.234375 C 213.003906 213.585938 180.546875 230.304688 161.140625 236.488281 C 156.6875 237.90625 152.183594 239.179688 147.625 240.3125 Z M 101.992188 258.078125 C 136.382812 256.734375 177.355469 248 217.675781 222.363281 L 209.90625 249.867188 L 254.910156 214.4375 L 302.539062 246.246094 L 282.71875 192.539062 L 327.71875 157.109375 L 270.46875 159.34375 L 250.648438 105.636719 L 235.085938 160.726562 L 177.835938 162.964844 L 210.980469 185.097656 C 189.164062 204.921875 134.445312 247.195312 61.957031 250.03125 C 47.300781 250.601562 31.914062 249.558594 15.933594 246.394531 C 15.933594 246.394531 52.011719 260.035156 101.992188 258.078125 " fill-opacity="1" fill-rule="nonzero"/>
33
+ </g>
34
+ <g clip-path="url(#183d3cc178)">
35
+ <path fill="#edb641" d="M 250.789062 78.96875 C 190.78125 78.96875 142.140625 127.570312 142.140625 187.519531 C 142.140625 198.875 143.886719 209.816406 147.121094 220.101562 C 151.847656 217.75 156.363281 215.316406 160.660156 212.84375 C 158.394531 204.789062 157.183594 196.296875 157.183594 187.519531 C 157.183594 135.871094 199.089844 93.996094 250.789062 93.996094 C 302.484375 93.996094 344.390625 135.871094 344.390625 187.519531 C 344.390625 239.171875 302.484375 281.042969 250.789062 281.042969 C 222.75 281.042969 197.597656 268.722656 180.441406 249.210938 C 175.453125 251.152344 170.402344 252.917969 165.289062 254.511719 C 185.183594 279.816406 216.082031 296.070312 250.789062 296.070312 C 310.792969 296.070312 359.433594 247.472656 359.433594 187.519531 C 359.433594 127.570312 310.792969 78.96875 250.789062 78.96875 " fill-opacity="1" fill-rule="nonzero"/>
36
+ </g>
37
+ <path fill="#edb641" d="M 92.292969 173.023438 L 98.289062 191.460938 L 117.691406 191.460938 L 101.992188 202.855469 L 107.988281 221.292969 L 92.292969 209.898438 L 76.59375 221.292969 L 82.589844 202.855469 L 66.894531 191.460938 L 86.296875 191.460938 L 92.292969 173.023438 " fill-opacity="1" fill-rule="nonzero"/>
38
+ <path fill="#edb641" d="M 120.214844 112.25 L 125.390625 128.167969 L 142.140625 128.167969 L 128.589844 138 L 133.765625 153.917969 L 120.214844 144.082031 L 106.664062 153.917969 L 111.839844 138 L 98.289062 128.167969 L 115.039062 128.167969 L 120.214844 112.25 " fill-opacity="1" fill-rule="nonzero"/>
39
+ <path fill="#edb641" d="M 34.695312 209.136719 L 37.71875 218.421875 L 47.492188 218.421875 L 39.585938 224.160156 L 42.605469 233.449219 L 34.695312 227.707031 L 26.792969 233.449219 L 29.8125 224.160156 L 21.90625 218.421875 L 31.679688 218.421875 L 34.695312 209.136719 " fill-opacity="1" fill-rule="nonzero"/>
40
+ </svg>
41
+ </a>
20
42
 
21
- <!-- Card -->
22
- <div class="relative bg-gray-900/60 backdrop-blur-lg border border-white/10 rounded-3xl shadow-2xl overflow-hidden">
43
+ <span aria-hidden="true" class="text-on-surface-subtle text-xl font-light leading-none">+</span>
23
44
 
24
- <!-- Top Accent Line -->
25
- <div class="h-1 w-full bg-linear-to-r from-transparent via-brand-accent to-transparent opacity-50"></div>
45
+ <a href="https://vite.dev" target="_blank" rel="noreferrer" class="block transition-opacity duration-200 hover:opacity-80" aria-label="Vite">
46
+ <svg viewBox="0 0 410 404" class="h-12 w-auto" fill="none" xmlns="http://www.w3.org/2000/svg">
47
+ <path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
48
+ <path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
49
+ <defs>
50
+ <linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
51
+ <stop stop-color="#41D1FF"/>
52
+ <stop offset="1" stop-color="#BD34FE"/>
53
+ </linearGradient>
54
+ <linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
55
+ <stop stop-color="#FFEA83"/>
56
+ <stop offset="0.0833333" stop-color="#FFDD35"/>
57
+ <stop offset="1" stop-color="#FFA800"/>
58
+ </linearGradient>
59
+ </defs>
60
+ </svg>
61
+ </a>
62
+ </div>
26
63
 
27
- <div class="px-8 py-10 sm:px-12 sm:py-12">
64
+ <h1 class="text-on-surface text-brand-glow font-light uppercase text-2xl sm:text-4xl mb-4" style="letter-spacing: 0.25em;">
65
+ Litestar Vite
66
+ </h1>
67
+ <p class="text-on-surface-subtle text-base">
68
+ Frontend Development Server
69
+ </p>
70
+ </header>
28
71
 
29
- <!-- Header / Logos -->
30
- <div class="flex items-center justify-center space-x-8 mb-10">
31
- <!-- Litestar Logo -->
32
- <a href="https://litestar.dev" target="_blank" class="group transition-transform hover:scale-110 duration-300 relative">
33
- <div class="absolute inset-0 bg-brand-accent/20 blur-xl rounded-full opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
34
- <svg viewBox="0 52 410 250" xmlns="http://www.w3.org/2000/svg" class="relative h-20 w-auto drop-shadow-lg">
35
- <defs>
36
- <clipPath id="9eb7762d41">
37
- <path d="M 15.933594 105 L 328 105 L 328 259 L 15.933594 259 Z M 15.933594 105 " clip-rule="nonzero"/>
38
- </clipPath>
39
- <clipPath id="183d3cc178">
40
- <path d="M 142 78.769531 L 359.433594 78.769531 L 359.433594 296.269531 L 142 296.269531 Z M 142 78.769531 " clip-rule="nonzero"/>
41
- </clipPath>
42
- </defs>
43
- <g clip-path="url(#9eb7762d41)">
44
- <path fill="#edb641" d="M 147.625 240.3125 C 161.5 233.984375 173.554688 227.011719 183.425781 220.550781 C 202.304688 208.203125 226.4375 185.242188 227.761719 183.410156 L 218.917969 177.503906 L 211.257812 172.386719 L 235.503906 171.441406 L 243.296875 171.136719 L 245.414062 163.640625 L 252.007812 140.304688 L 260.402344 163.054688 L 263.097656 170.363281 L 270.890625 170.058594 L 295.136719 169.113281 L 276.078125 184.117188 L 269.953125 188.9375 L 272.652344 196.25 L 281.046875 218.996094 L 260.871094 205.523438 L 254.390625 201.195312 L 248.265625 206.015625 L 229.207031 221.023438 L 232.480469 209.425781 L 235.796875 197.691406 L 236.207031 196.234375 C 213.003906 213.585938 180.546875 230.304688 161.140625 236.488281 C 156.6875 237.90625 152.183594 239.179688 147.625 240.3125 Z M 101.992188 258.078125 C 136.382812 256.734375 177.355469 248 217.675781 222.363281 L 209.90625 249.867188 L 254.910156 214.4375 L 302.539062 246.246094 L 282.71875 192.539062 L 327.71875 157.109375 L 270.46875 159.34375 L 250.648438 105.636719 L 235.085938 160.726562 L 177.835938 162.964844 L 210.980469 185.097656 C 189.164062 204.921875 134.445312 247.195312 61.957031 250.03125 C 47.300781 250.601562 31.914062 249.558594 15.933594 246.394531 C 15.933594 246.394531 52.011719 260.035156 101.992188 258.078125 " fill-opacity="1" fill-rule="nonzero"/>
45
- </g>
46
- <g clip-path="url(#183d3cc178)">
47
- <path fill="#edb641" d="M 250.789062 78.96875 C 190.78125 78.96875 142.140625 127.570312 142.140625 187.519531 C 142.140625 198.875 143.886719 209.816406 147.121094 220.101562 C 151.847656 217.75 156.363281 215.316406 160.660156 212.84375 C 158.394531 204.789062 157.183594 196.296875 157.183594 187.519531 C 157.183594 135.871094 199.089844 93.996094 250.789062 93.996094 C 302.484375 93.996094 344.390625 135.871094 344.390625 187.519531 C 344.390625 239.171875 302.484375 281.042969 250.789062 281.042969 C 222.75 281.042969 197.597656 268.722656 180.441406 249.210938 C 175.453125 251.152344 170.402344 252.917969 165.289062 254.511719 C 185.183594 279.816406 216.082031 296.070312 250.789062 296.070312 C 310.792969 296.070312 359.433594 247.472656 359.433594 187.519531 C 359.433594 127.570312 310.792969 78.96875 250.789062 78.96875 " fill-opacity="1" fill-rule="nonzero"/>
48
- </g>
49
- <path fill="#edb641" d="M 92.292969 173.023438 L 98.289062 191.460938 L 117.691406 191.460938 L 101.992188 202.855469 L 107.988281 221.292969 L 92.292969 209.898438 L 76.59375 221.292969 L 82.589844 202.855469 L 66.894531 191.460938 L 86.296875 191.460938 L 92.292969 173.023438 " fill-opacity="1" fill-rule="nonzero"/>
50
- <path fill="#edb641" d="M 120.214844 112.25 L 125.390625 128.167969 L 142.140625 128.167969 L 128.589844 138 L 133.765625 153.917969 L 120.214844 144.082031 L 106.664062 153.917969 L 111.839844 138 L 98.289062 128.167969 L 115.039062 128.167969 L 120.214844 112.25 " fill-opacity="1" fill-rule="nonzero"/>
51
- <path fill="#edb641" d="M 34.695312 209.136719 L 37.71875 218.421875 L 47.492188 218.421875 L 39.585938 224.160156 L 42.605469 233.449219 L 34.695312 227.707031 L 26.792969 233.449219 L 29.8125 224.160156 L 21.90625 218.421875 L 31.679688 218.421875 L 34.695312 209.136719 " fill-opacity="1" fill-rule="nonzero"/>
52
- </svg>
53
- </a>
72
+ <!-- Status surface: bordered, no glass; "borders over shadows" -->
73
+ <section class="rounded-brand-lg border border-outline bg-navy-surface">
54
74
 
55
- <!-- Connection Icon -->
56
- <div class="flex items-center text-brand-gray/30">
57
- <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
58
- <path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
59
- </svg>
60
- </div>
61
-
62
- <!-- Vite Logo -->
63
- <a href="https://vite.dev" target="_blank" class="group transition-transform hover:scale-110 duration-300 relative">
64
- <div class="absolute inset-0 bg-purple-500/20 blur-xl rounded-full opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
65
- <svg viewBox="0 0 410 404" class="relative h-16 w-auto drop-shadow-lg" fill="none" xmlns="http://www.w3.org/2000/svg">
66
- <path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
67
- <path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
68
- <defs>
69
- <linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
70
- <stop stop-color="#41D1FF"/>
71
- <stop offset="1" stop-color="#BD34FE"/>
72
- </linearGradient>
73
- <linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
74
- <stop stop-color="#FFEA83"/>
75
- <stop offset="0.0833333" stop-color="#FFDD35"/>
76
- <stop offset="1" stop-color="#FFA800"/>
77
- </linearGradient>
78
- </defs>
79
- </svg>
80
- </a>
81
- </div>
82
-
83
- <!-- Content -->
84
- <div class="relative text-center space-y-8">
85
- <div>
86
- <h1 class="text-4xl sm:text-5xl font-bold text-white tracking-tight mb-2">
87
- Litestar <span class="text-transparent bg-clip-text bg-linear-to-r from-brand-accent to-brand-accent-light">Vite</span>
88
- </h1>
89
- <p class="text-brand-gray/60 text-lg font-medium">Frontend Development Server</p>
90
- </div>
91
-
92
- <div class="p-6 rounded-2xl bg-white/5 border border-white/10 shadow-inner">
93
- <div class="flex items-center justify-center space-x-3 mb-4">
94
- <span class="relative flex h-3 w-3">
95
- <span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-brand-success opacity-75"></span>
96
- <span class="relative inline-flex rounded-full h-3 w-3 bg-brand-success"></span>
97
- </span>
98
- <span class="text-sm font-semibold text-brand-success tracking-wide uppercase">Server Active</span>
99
- </div>
75
+ <!-- Status row -->
76
+ <div class="flex items-center gap-3 px-8 py-5 border-b border-outline">
77
+ <span class="relative inline-flex h-2.5 w-2.5">
78
+ <span class="absolute inline-flex h-full w-full rounded-full bg-success opacity-60 animate-pulse-dot"></span>
79
+ <span class="relative inline-flex h-2.5 w-2.5 rounded-full bg-success"></span>
80
+ </span>
81
+ <span class="text-on-surface text-xs font-semibold uppercase tracking-widest">
82
+ Server Active
83
+ </span>
84
+ <span aria-hidden="true" class="ml-auto text-on-surface-subtle text-xs font-mono uppercase tracking-widest">
85
+ HMR
86
+ </span>
87
+ </div>
100
88
 
101
- <p class="text-brand-gray leading-relaxed">
102
- This server provides <strong class="text-white">Hot Module Replacement (HMR)</strong> for your assets.
103
- To view your application, ensure your Litestar backend is running.
104
- </p>
105
- </div>
89
+ <div class="px-8 py-8 sm:px-10 sm:py-10 space-y-8">
106
90
 
107
- <div class="space-y-2">
108
- <p class="text-brand-gray/80">
109
- Your application is configured at:
110
- </p>
111
- <div class="inline-block group">
112
- <a href="{{ APP_URL }}" class="text-2xl font-mono font-bold text-brand-accent group-hover:text-brand-accent-light transition-colors">
113
- {{ APP_URL }}
114
- </a>
115
- <div class="h-px w-full bg-brand-accent/30 group-hover:bg-brand-accent transition-colors mt-1"></div>
116
- </div>
117
- </div>
118
- </div>
91
+ <p class="text-on-surface-muted text-base leading-relaxed">
92
+ This server provides
93
+ <strong class="text-on-surface font-medium">Hot Module Replacement</strong>
94
+ for your assets. To view your application, ensure your Litestar backend is running.
95
+ </p>
119
96
 
120
- <!-- Footer -->
121
- <div class="mt-12 pt-8 border-t border-white/10 flex justify-center">
122
- <a href="https://docs.litestar.dev/latest/topics/integrations/vite/" target="_blank" class="group inline-flex items-center px-5 py-2.5 rounded-full bg-white/5 hover:bg-white/10 border border-white/10 hover:border-brand-accent/50 transition-all duration-300">
123
- <span class="text-sm font-semibold text-brand-gray group-hover:text-white transition-colors">Documentation</span>
124
- <svg xmlns="http://www.w3.org/2000/svg" class="ml-2 h-4 w-4 text-brand-accent group-hover:translate-x-1 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
97
+ <div>
98
+ <p class="text-on-surface-subtle text-xs font-semibold uppercase tracking-widest mb-3">
99
+ Application URL
100
+ </p>
101
+ <a href="{{ APP_URL }}"
102
+ class="group flex w-full items-center gap-3 rounded-brand border-l-[3px] border-l-gold border border-outline bg-navy-surface-variant px-4 py-3 font-mono text-base text-gold transition-colors duration-200 hover:bg-navy hover:text-gold-light">
103
+ <span class="min-w-0 flex-1 break-all select-all">{{ APP_URL }}</span>
104
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5 flex-none opacity-60 transition-opacity group-hover:opacity-100" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
125
105
  <path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3" />
126
106
  </svg>
127
107
  </a>
128
108
  </div>
129
109
  </div>
130
- </div>
131
- </div>
110
+ </section>
111
+
112
+ <!-- Footer: restrained bordered link, no pill/glass -->
113
+ <footer class="mt-8 flex justify-center">
114
+ <a href="https://docs.litestar.dev/latest/topics/integrations/vite/" target="_blank" rel="noreferrer"
115
+ class="group inline-flex items-center gap-2 text-sm text-on-surface-muted transition-colors duration-200 hover:text-gold">
116
+ <span>Documentation</span>
117
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5 transition-transform duration-200 group-hover:translate-x-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
118
+ <path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3" />
119
+ </svg>
120
+ </a>
121
+ </footer>
122
+ </main>
132
123
  </body>
133
124
  </html>
@@ -188,7 +188,7 @@ export interface PluginConfig {
188
188
  * page directing users to access the app through the backend (even if an
189
189
  * index.html exists for the backend to render).
190
190
  *
191
- * Auto-detected from `.litestar.json` when mode is "inertia".
191
+ * Auto-detected from `.litestar.json` when Inertia is configured.
192
192
  *
193
193
  * @default false (auto-detected from .litestar.json)
194
194
  */
package/dist/js/index.js CHANGED
@@ -86,6 +86,7 @@ function resolveLitestarPlugin(pluginConfig) {
86
86
  const runtimeAssetUrl = normalizeAssetUrl(env.ASSET_URL || pluginConfig.assetUrl);
87
87
  const buildAssetUrl = pluginConfig.deployAssetUrl ?? runtimeAssetUrl;
88
88
  const serverConfig = command === "serve" ? resolveDevelopmentEnvironmentServerConfig(pluginConfig.detectTls) ?? resolveEnvironmentServerConfig(env) : void 0;
89
+ const effectiveAppUrl = env.APP_URL || pythonDefaults?.appUrl || void 0;
89
90
  const withProxyErrorSilencer = (proxyConfig) => {
90
91
  if (!proxyConfig) return void 0;
91
92
  return Object.fromEntries(
@@ -117,6 +118,7 @@ function resolveLitestarPlugin(pluginConfig) {
117
118
  };
118
119
  const explicitServerOrigin = typeof userConfig.server?.origin === "string" && userConfig.server.origin.length > 0 ? userConfig.server.origin : void 0;
119
120
  const shouldForceDirectServerOrigin = explicitServerOrigin !== void 0 || pythonDefaults?.proxyMode === "direct";
121
+ const proxyOriginDefault = !explicitServerOrigin && pythonDefaults?.proxyMode === "vite" && pythonDefaults.appUrl ? pythonDefaults.appUrl : void 0;
120
122
  const devBase = pluginConfig.assetUrl.startsWith("/") ? pluginConfig.assetUrl : pluginConfig.assetUrl.replace(/\/+$/, "");
121
123
  ensureCommandShouldRunInEnvironment(command, env, mode);
122
124
  return {
@@ -131,7 +133,7 @@ function resolveLitestarPlugin(pluginConfig) {
131
133
  assetsInlineLimit: userConfig.build?.assetsInlineLimit ?? 0
132
134
  },
133
135
  server: {
134
- origin: shouldForceDirectServerOrigin ? explicitServerOrigin ?? "__litestar_vite_placeholder__" : void 0,
136
+ origin: shouldForceDirectServerOrigin ? explicitServerOrigin ?? "__litestar_vite_placeholder__" : proxyOriginDefault,
135
137
  // Auto-configure HMR to use a path that routes through Litestar proxy
136
138
  // Note: Vite automatically prepends `base` to `hmr.path`, so we just use "vite-hmr"
137
139
  // Result: base="/static/" + path="vite-hmr" = "/static/vite-hmr"
@@ -144,14 +146,14 @@ function resolveLitestarPlugin(pluginConfig) {
144
146
  // This allows the app to work when accessing Vite directly (not through Litestar proxy)
145
147
  // Only proxies /api and /schema routes - everything else is handled by Vite
146
148
  proxy: withProxyErrorSilencer(
147
- userConfig.server?.proxy ?? (env.APP_URL ? {
149
+ userConfig.server?.proxy ?? (effectiveAppUrl ? {
148
150
  "/api": {
149
- target: env.APP_URL,
151
+ target: effectiveAppUrl,
150
152
  changeOrigin: true,
151
153
  ws: true
152
154
  },
153
155
  "/schema": {
154
- target: env.APP_URL,
156
+ target: effectiveAppUrl,
155
157
  changeOrigin: true,
156
158
  ws: true
157
159
  }
@@ -242,7 +244,9 @@ function resolveLitestarPlugin(pluginConfig) {
242
244
  const isAddressInfo = (x) => typeof x === "object";
243
245
  if (isAddressInfo(address)) {
244
246
  const explicitServerOrigin = typeof userConfig.server?.origin === "string" && userConfig.server.origin.length > 0 ? userConfig.server.origin : void 0;
245
- viteDevServerUrl = explicitServerOrigin ? explicitServerOrigin : resolveDevServerUrl(address, server.config, userConfig);
247
+ const configuredServerOrigin = typeof server.config.server.origin === "string" && server.config.server.origin.length > 0 && server.config.server.origin !== "__litestar_vite_placeholder__" ? server.config.server.origin : void 0;
248
+ const hotfileOrigin = configuredServerOrigin ?? explicitServerOrigin;
249
+ viteDevServerUrl = hotfileOrigin ? hotfileOrigin : resolveDevServerUrl(address, server.config, userConfig);
246
250
  fs.mkdirSync(path.dirname(pluginConfig.hotFile), { recursive: true });
247
251
  fs.writeFileSync(pluginConfig.hotFile, viteDevServerUrl);
248
252
  setTimeout(async () => {
@@ -650,7 +654,8 @@ function resolvePluginConfig(config) {
650
654
  typesConfig.schemasTsPath = path.join(typesConfig.output, "schemas.ts");
651
655
  }
652
656
  }
653
- const inertiaMode = resolvedConfig.inertiaMode ?? (pythonDefaults?.mode === "hybrid" || pythonDefaults?.mode === "inertia");
657
+ const bridgeInertiaEnabled = pythonDefaults?.spa !== null && pythonDefaults?.spa !== void 0;
658
+ const inertiaMode = resolvedConfig.inertiaMode ?? bridgeInertiaEnabled;
654
659
  const effectiveResourceDir = resolvedConfig.resourceDir ?? pythonDefaults?.resourceDir ?? "src";
655
660
  const resolvedBundleDir = resolvedConfig.bundleDir ?? pythonDefaults?.bundleDir ?? "public";
656
661
  const resolvedStaticDir = resolvedConfig.staticDir ?? pythonDefaults?.staticDir ?? path.join(effectiveResourceDir, "public");
@@ -700,7 +705,7 @@ function validateAgainstPythonDefaults(resolved, pythonDefaults, userConfig) {
700
705
  if (userConfig.staticDir !== void 0 && hasPythonValue(pythonDefaults.staticDir) && !pathsAreSame(resolved.staticDir, pythonDefaults.staticDir)) {
701
706
  warnings.push(`staticDir: vite.config.ts="${resolved.staticDir}" differs from Python="${pythonDefaults.staticDir}"`);
702
707
  }
703
- const frameworkMode = pythonDefaults.mode === "framework" || pythonDefaults.mode === "ssr" || pythonDefaults.mode === "ssg";
708
+ const frameworkMode = pythonDefaults.mode === "framework";
704
709
  if (frameworkMode && userConfig.ssrOutDir !== void 0 && hasPythonValue(pythonDefaults.ssrOutDir) && !pathsAreSame(resolved.ssrOutDir, pythonDefaults.ssrOutDir)) {
705
710
  warnings.push(`ssrOutDir: vite.config.ts="${resolved.ssrOutDir}" differs from Python="${pythonDefaults.ssrOutDir}"`);
706
711
  }
@@ -27,13 +27,33 @@ export function unwrapPageProps(props) {
27
27
  }
28
28
  return props;
29
29
  }
30
+ // Cache wrapped results keyed by the source module/function. Vite's import.meta.glob
31
+ // memoizes resolved modules, so the same path yields the same input object across
32
+ // resolves; caching keeps the wrapper identity stable. Inertia's Pages.set() calls
33
+ // resolveComponent on every navigation/partial-reload — without this, React would
34
+ // see a new element type each call and unmount the page subtree (wiping useState
35
+ // and re-running effects). HMR-replaced modules are new objects, so they re-wrap.
36
+ const wrapCache = new WeakMap();
30
37
  /**
31
38
  * Wrap a component to automatically unwrap Litestar's content prop.
32
39
  *
40
+ * Memoized by the input module reference so the same module always yields the
41
+ * same wrapper, preserving component identity across Inertia's repeated resolve
42
+ * calls.
43
+ *
33
44
  * @param component - The original component (function or object with default)
34
45
  * @returns Wrapped component that transforms props
35
46
  */
36
47
  function wrapComponent(module) {
48
+ if (module === null || (typeof module !== "object" && typeof module !== "function")) {
49
+ return module;
50
+ }
51
+ const key = module;
52
+ const cached = wrapCache.get(key);
53
+ if (cached !== undefined) {
54
+ return cached;
55
+ }
56
+ let result = module;
37
57
  // Handle ES module with default export
38
58
  const mod = module;
39
59
  if (mod.default && typeof mod.default === "function") {
@@ -41,16 +61,17 @@ function wrapComponent(module) {
41
61
  const Wrapped = (props) => Original(unwrapPageProps(props));
42
62
  // Copy static properties (displayName, layout, etc.)
43
63
  Object.assign(Wrapped, Original);
44
- return { ...mod, default: Wrapped };
64
+ result = { ...mod, default: Wrapped };
45
65
  }
46
- // Handle direct function export
47
- if (typeof module === "function") {
66
+ else if (typeof module === "function") {
67
+ // Handle direct function export
48
68
  const Original = module;
49
69
  const Wrapped = (props) => Original(unwrapPageProps(props));
50
70
  Object.assign(Wrapped, Original);
51
- return Wrapped;
71
+ result = Wrapped;
52
72
  }
53
- return module;
73
+ wrapCache.set(key, result);
74
+ return result;
54
75
  }
55
76
  export async function resolvePageComponent(path, pages) {
56
77
  for (const p of Array.isArray(path) ? path : [path]) {
package/dist/js/nuxt.js CHANGED
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import colors from "picocolors";
4
4
  import { readBridgeConfig } from "./shared/bridge-schema.js";
5
5
  import { DEBOUNCE_MS } from "./shared/constants.js";
6
- import { normalizeHost, resolveHotFilePath } from "./shared/network.js";
6
+ import { normalizeHost, resolveHotFilePath, resolveLitestarPort } from "./shared/network.js";
7
7
  import { createLitestarTypeGenPlugin } from "./shared/typegen-plugin.js";
8
8
  function resolveConfig(config = {}) {
9
9
  let hotFile;
@@ -19,6 +19,8 @@ function resolveConfig(config = {}) {
19
19
  }
20
20
  }
21
21
  let pythonExecutor;
22
+ let assetUrl;
23
+ let litestarPort;
22
24
  const runtime = readBridgeConfig();
23
25
  if (runtime) {
24
26
  hasPythonConfig = true;
@@ -27,10 +29,15 @@ function resolveConfig(config = {}) {
27
29
  proxyMode = runtime.proxyMode;
28
30
  devPort = runtime.port;
29
31
  pythonExecutor = runtime.executor;
32
+ assetUrl = runtime.assetUrl;
30
33
  if (runtime.types) {
31
34
  pythonTypesConfig = runtime.types;
32
35
  }
33
36
  }
37
+ const resolvedLitestarPort = resolveLitestarPort(runtime?.litestarPort, runtime?.appUrl);
38
+ if (resolvedLitestarPort !== null) {
39
+ litestarPort = resolvedLitestarPort;
40
+ }
34
41
  let typesConfig = false;
35
42
  const defaultTypesOutput = "generated";
36
43
  const buildTypeDefaults = (output) => ({
@@ -107,6 +114,8 @@ function resolveConfig(config = {}) {
107
114
  hotFile,
108
115
  proxyMode,
109
116
  devPort,
117
+ litestarPort,
118
+ assetUrl,
110
119
  executor: config.executor ?? pythonExecutor,
111
120
  hasPythonConfig
112
121
  };
@@ -131,6 +140,8 @@ function createProxyPlugin(config) {
131
140
  name: "litestar-nuxt-proxy",
132
141
  async config() {
133
142
  hmrPort = await getPort();
143
+ const hmrPath = `${(config.assetUrl ?? "/static").replace(/\/$/, "")}/vite-hmr`;
144
+ const browserHmrPort = config.litestarPort ?? config.devPort;
134
145
  return {
135
146
  server: {
136
147
  // Force IPv4 binding for consistency with Python proxy configuration
@@ -142,11 +153,13 @@ function createProxyPlugin(config) {
142
153
  port: config.devPort,
143
154
  strictPort: true
144
155
  } : {},
145
- // Avoid HMR port collisions by letting Vite pick a free port for WS
156
+ // Vite serves HMR on a separate internal port; browsers reach it through
157
+ // Litestar's /static/vite-hmr WebSocket handler.
146
158
  hmr: {
147
159
  port: hmrPort,
148
160
  host: "127.0.0.1",
149
- clientPort: config.devPort
161
+ ...browserHmrPort !== void 0 ? { clientPort: browserHmrPort } : {},
162
+ ...config.litestarPort !== void 0 ? { path: hmrPath, protocol: "ws" } : {}
150
163
  }
151
164
  }
152
165
  };
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @module
10
10
  */
11
- export type BridgeMode = "spa" | "template" | "htmx" | "hybrid" | "inertia" | "framework" | "ssr" | "ssg" | "external";
11
+ export type BridgeMode = "spa" | "template" | "hybrid" | "framework" | "external";
12
12
  export type BridgeProxyMode = "vite" | "direct" | "proxy" | null;
13
13
  export type BridgeExecutor = "node" | "bun" | "deno" | "yarn" | "pnpm";
14
14
  export interface BridgeTypesConfig {
@@ -33,6 +33,14 @@ export interface BridgeSpaConfig {
33
33
  export interface BridgeSchema {
34
34
  assetUrl: string;
35
35
  deployAssetUrl: string | null;
36
+ appUrl: string | null;
37
+ /**
38
+ * Litestar dev server port. Used by framework integrations to set
39
+ * `vite.server.hmr.clientPort`, ensuring the browser connects to Litestar
40
+ * (not the framework dev server) for HMR — preserving the single-port
41
+ * contract.
42
+ */
43
+ litestarPort: number | null;
36
44
  bundleDir: string;
37
45
  resourceDir: string;
38
46
  staticDir: string;
@@ -3,6 +3,8 @@ import path from "node:path";
3
3
  const allowedTopLevelKeys = /* @__PURE__ */ new Set([
4
4
  "assetUrl",
5
5
  "deployAssetUrl",
6
+ "appUrl",
7
+ "litestarPort",
6
8
  "bundleDir",
7
9
  "resourceDir",
8
10
  "staticDir",
@@ -20,7 +22,7 @@ const allowedTopLevelKeys = /* @__PURE__ */ new Set([
20
22
  "litestarVersion",
21
23
  "staticProps"
22
24
  ]);
23
- const allowedModes = /* @__PURE__ */ new Set(["spa", "template", "htmx", "hybrid", "inertia", "framework", "ssr", "ssg", "external"]);
25
+ const allowedModes = /* @__PURE__ */ new Set(["spa", "template", "hybrid", "framework", "external"]);
24
26
  const allowedProxyModes = /* @__PURE__ */ new Set(["vite", "direct", "proxy"]);
25
27
  const allowedExecutors = /* @__PURE__ */ new Set(["node", "bun", "deno", "yarn", "pnpm"]);
26
28
  const allowedLogLevels = /* @__PURE__ */ new Set(["quiet", "normal", "verbose"]);
@@ -62,6 +64,22 @@ function assertNullableString(obj, key) {
62
64
  }
63
65
  return value;
64
66
  }
67
+ function assertOptionalNullableString(obj, key) {
68
+ const value = obj[key];
69
+ if (value === void 0 || value === null) return null;
70
+ if (typeof value !== "string") {
71
+ fail(`"${key}" must be a string or null`);
72
+ }
73
+ return value;
74
+ }
75
+ function assertOptionalNullableInteger(obj, key) {
76
+ const value = obj[key];
77
+ if (value === void 0 || value === null) return null;
78
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
79
+ fail(`"${key}" must be a positive integer or null`);
80
+ }
81
+ return value;
82
+ }
65
83
  function assertOptionalString(obj, key) {
66
84
  const value = obj[key];
67
85
  if (value === void 0) return void 0;
@@ -149,6 +167,8 @@ function parseBridgeSchema(value) {
149
167
  }
150
168
  const assetUrl = assertString(obj, "assetUrl");
151
169
  const deployAssetUrl = assertNullableString(obj, "deployAssetUrl");
170
+ const appUrl = assertOptionalNullableString(obj, "appUrl");
171
+ const litestarPort = assertOptionalNullableInteger(obj, "litestarPort");
152
172
  const bundleDir = assertString(obj, "bundleDir");
153
173
  const resourceDir = assertString(obj, "resourceDir");
154
174
  const staticDir = assertString(obj, "staticDir");
@@ -168,6 +188,8 @@ function parseBridgeSchema(value) {
168
188
  return {
169
189
  assetUrl,
170
190
  deployAssetUrl,
191
+ appUrl,
192
+ litestarPort,
171
193
  bundleDir,
172
194
  resourceDir,
173
195
  staticDir,
@@ -21,6 +21,21 @@
21
21
  * ```
22
22
  */
23
23
  export declare function normalizeHost(host: string): string;
24
+ /**
25
+ * Resolve the Litestar dev server port for HMR routing.
26
+ *
27
+ * Framework integrations (Astro/Nuxt/SvelteKit) need this port to set
28
+ * `vite.server.hmr.clientPort` so the browser opens the HMR WebSocket against
29
+ * Litestar — NOT the framework dev server's port — preserving the
30
+ * single-port-via-ASGI contract.
31
+ *
32
+ * Resolution order:
33
+ * 1. `bridge.litestarPort` (preferred; written by Python ≥0.23.0).
34
+ * 2. Parse `bridge.appUrl` (works with older bridges that lack `litestarPort`).
35
+ * 3. `LITESTAR_PORT` / `PORT` env var.
36
+ * 4. `null` if no signal.
37
+ */
38
+ export declare function resolveLitestarPort(bridgeLitestarPort: number | null | undefined, bridgeAppUrl: string | null | undefined, env?: NodeJS.ProcessEnv): number | null;
24
39
  /**
25
40
  * Resolve the absolute hot file path from bundleDir + hotFile.
26
41
  *
@@ -8,6 +8,29 @@ function normalizeHost(host) {
8
8
  }
9
9
  return host;
10
10
  }
11
+ function resolveLitestarPort(bridgeLitestarPort, bridgeAppUrl, env = process.env) {
12
+ if (typeof bridgeLitestarPort === "number" && Number.isInteger(bridgeLitestarPort) && bridgeLitestarPort > 0) {
13
+ return bridgeLitestarPort;
14
+ }
15
+ if (typeof bridgeAppUrl === "string" && bridgeAppUrl.length > 0) {
16
+ try {
17
+ const parsed = new URL(bridgeAppUrl);
18
+ if (parsed.port) {
19
+ const p = Number.parseInt(parsed.port, 10);
20
+ if (!Number.isNaN(p) && p > 0) return p;
21
+ }
22
+ if (parsed.protocol === "https:") return 443;
23
+ if (parsed.protocol === "http:") return 80;
24
+ } catch {
25
+ }
26
+ }
27
+ const raw = env.LITESTAR_PORT ?? env.PORT;
28
+ if (raw) {
29
+ const p = Number.parseInt(raw, 10);
30
+ if (!Number.isNaN(p) && p > 0) return p;
31
+ }
32
+ return null;
33
+ }
11
34
  function resolveHotFilePath(bundleDir, hotFile, rootDir = process.cwd()) {
12
35
  if (path.isAbsolute(hotFile)) {
13
36
  return hotFile;
@@ -21,5 +44,6 @@ function resolveHotFilePath(bundleDir, hotFile, rootDir = process.cwd()) {
21
44
  }
22
45
  export {
23
46
  normalizeHost,
24
- resolveHotFilePath
47
+ resolveHotFilePath,
48
+ resolveLitestarPort
25
49
  };
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import colors from "picocolors";
4
4
  import { readBridgeConfig } from "./shared/bridge-schema.js";
5
5
  import { DEBOUNCE_MS } from "./shared/constants.js";
6
- import { normalizeHost, resolveHotFilePath } from "./shared/network.js";
6
+ import { normalizeHost, resolveHotFilePath, resolveLitestarPort } from "./shared/network.js";
7
7
  import { createLitestarTypeGenPlugin } from "./shared/typegen-plugin.js";
8
8
  function resolveConfig(config = {}) {
9
9
  let hotFile;
@@ -19,6 +19,8 @@ function resolveConfig(config = {}) {
19
19
  }
20
20
  }
21
21
  let pythonExecutor;
22
+ let assetUrl;
23
+ let litestarPort;
22
24
  const runtime = readBridgeConfig();
23
25
  if (runtime) {
24
26
  hasPythonConfig = true;
@@ -27,10 +29,15 @@ function resolveConfig(config = {}) {
27
29
  proxyMode = runtime.proxyMode;
28
30
  port = runtime.port;
29
31
  pythonExecutor = runtime.executor;
32
+ assetUrl = runtime.assetUrl;
30
33
  if (runtime.types) {
31
34
  pythonTypesConfig = runtime.types;
32
35
  }
33
36
  }
37
+ const resolvedLitestarPort = resolveLitestarPort(runtime?.litestarPort, runtime?.appUrl);
38
+ if (resolvedLitestarPort !== null) {
39
+ litestarPort = resolvedLitestarPort;
40
+ }
34
41
  let typesConfig = false;
35
42
  const defaultTypesOutput = "src/lib/generated";
36
43
  const buildTypeDefaults = (output) => ({
@@ -107,6 +114,8 @@ function resolveConfig(config = {}) {
107
114
  hotFile,
108
115
  proxyMode,
109
116
  port,
117
+ litestarPort,
118
+ assetUrl,
110
119
  executor: config.executor ?? pythonExecutor,
111
120
  hasPythonConfig
112
121
  };
@@ -118,6 +127,7 @@ function litestarSvelteKit(userConfig = {}) {
118
127
  name: "litestar-sveltekit",
119
128
  enforce: "pre",
120
129
  config() {
130
+ const hmrPath = `${(config.assetUrl ?? "/static").replace(/\/$/, "")}/vite-hmr`;
121
131
  return {
122
132
  server: {
123
133
  // Force IPv4 binding for consistency with Python proxy configuration
@@ -129,6 +139,15 @@ function litestarSvelteKit(userConfig = {}) {
129
139
  port: config.port,
130
140
  strictPort: true
131
141
  } : {},
142
+ // Route HMR through the Litestar port so DevTools never sees the framework port.
143
+ ...config.litestarPort !== void 0 ? {
144
+ hmr: {
145
+ protocol: "ws",
146
+ host: "127.0.0.1",
147
+ clientPort: config.litestarPort,
148
+ path: hmrPath
149
+ }
150
+ } : {},
132
151
  proxy: {
133
152
  [config.apiPrefix]: {
134
153
  target: config.apiProxy,
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "litestar-vite-plugin",
3
- "version": "0.22.1",
3
+ "version": "0.23.0",
4
4
  "type": "module",
5
5
  "description": "Litestar plugin for Vite.",
6
6
  "keywords": [
@@ -90,7 +90,7 @@
90
90
  "build-plugin": "rm -rf dist/js && npm run build-plugin-types && npm run build-plugin-esm && npm run build-dev-server",
91
91
  "build-dev-server": "vite build --config src/js/src/dev-server/vite.config.ts && mv dist/js/index.html dist/js/dev-server-index.html",
92
92
  "build-plugin-types": "tsc --project src/js/tsconfig.json --emitDeclarationOnly",
93
- "build-plugin-esm": "esbuild src/js/src/index.ts --platform=node --format=esm --outfile=dist/js/index.js && esbuild src/js/src/install-hint.ts --platform=node --format=esm --outfile=dist/js/install-hint.js && esbuild src/js/src/litestar-meta.ts --platform=node --format=esm --outfile=dist/js/litestar-meta.js && esbuild src/js/src/typegen-cli.ts --platform=node --format=esm --outfile=dist/js/typegen-cli.js --banner:js='#!/usr/bin/env node' && mkdir -p dist/js/shared && esbuild src/js/src/shared/bridge-schema.ts src/js/src/shared/constants.ts src/js/src/shared/debounce.ts src/js/src/shared/format-path.ts src/js/src/shared/logger.ts src/js/src/shared/emit-page-props-types.ts src/js/src/shared/emit-schemas-types.ts src/js/src/shared/emit-static-props-types.ts src/js/src/shared/typegen-plugin.ts src/js/src/shared/typegen-core.ts src/js/src/shared/write-if-changed.ts src/js/src/shared/typegen-cache.ts src/js/src/shared/network.ts src/js/src/shared/vite-compat.ts --platform=node --format=esm --outdir=dist/js/shared",
93
+ "build-plugin-esm": "esbuild src/js/src/index.ts --platform=node --format=esm --outfile=dist/js/index.js && esbuild src/js/src/install-hint.ts --platform=node --format=esm --outfile=dist/js/install-hint.js && esbuild src/js/src/litestar-meta.ts --platform=node --format=esm --outfile=dist/js/litestar-meta.js && esbuild src/js/src/typegen-cli.ts --platform=node --format=esm --outfile=dist/js/typegen-cli.js --banner:js='#!/usr/bin/env node' && chmod +x dist/js/typegen-cli.js && mkdir -p dist/js/shared && esbuild src/js/src/shared/bridge-schema.ts src/js/src/shared/constants.ts src/js/src/shared/debounce.ts src/js/src/shared/format-path.ts src/js/src/shared/logger.ts src/js/src/shared/emit-page-props-types.ts src/js/src/shared/emit-schemas-types.ts src/js/src/shared/emit-static-props-types.ts src/js/src/shared/typegen-plugin.ts src/js/src/shared/typegen-core.ts src/js/src/shared/write-if-changed.ts src/js/src/shared/typegen-cache.ts src/js/src/shared/network.ts src/js/src/shared/vite-compat.ts --platform=node --format=esm --outdir=dist/js/shared",
94
94
  "build-helpers": "rm -rf dist/js/helpers && tsc --project src/js/tsconfig.helpers.json",
95
95
  "build-inertia-helpers": "rm -rf dist/js/inertia-helpers && tsc --project src/js/tsconfig.inertia-helpers.json",
96
96
  "build-integrations": "esbuild src/js/src/astro.ts src/js/src/sveltekit.ts src/js/src/nuxt.ts src/js/src/inertia-types.ts --platform=node --format=esm --outdir=dist/js",