How to use Design Studio
- Describe your website. Open Guided design, enter your product and audience, then choose colors, shapes and spacing with Next.
- Generate and compare. Click Generate three directions, review an alternative, then choose Use reviewed proposal.
- Refine it. Try “make buttons square and use blue as the primary color” under Edit with words. Lock decisions you want to keep.
- Use it in your app. Open Review & apply and export Theme config or Compiled CSS. This website previews and exports designs; it does not change your deployed app automatically.
${configured ? "APIMart AI is enabled with gpt-6-luna. Generation sends your brief and current design to APIMart only when you request it." : "AI is not configured. Local presets and supported rule-based edits still work. Set APIMART_API_KEY in the server .env and restart to enable gpt-6-luna."}
`;
diff --git a/app/lib/studio-origin.ts b/app/lib/studio-origin.ts
new file mode 100644
index 0000000..61883fa
--- /dev/null
+++ b/app/lib/studio-origin.ts
@@ -0,0 +1,43 @@
+/** Use an explicit external origin, rather than trusting proxy-supplied schemes. */
+export function studioRequest(
+ request: Request,
+ publicOrigin = process.env.STUDIO_PUBLIC_ORIGIN ??
+ "https://component.wrnexusjs.dev",
+): Request | Response {
+ const incoming = new URL(request.url);
+ const configured = new URL(publicOrigin);
+ if (
+ configured.protocol !== "https:" ||
+ configured.username ||
+ configured.password ||
+ configured.pathname !== "/" ||
+ configured.search ||
+ configured.hash
+ )
+ throw Error(
+ "STUDIO_PUBLIC_ORIGIN must be an HTTPS origin without a path or credentials.",
+ );
+ const origin = request.headers.get("origin");
+ const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(
+ incoming.hostname,
+ );
+ const expected =
+ loopback && (!origin || origin === incoming.origin)
+ ? incoming.origin
+ : configured.origin;
+ if (origin && origin !== expected)
+ return Response.json(
+ { error: "Same-origin requests only." },
+ { status: 403, headers: { "cache-control": "no-store" } },
+ );
+ const normalized = new Request(
+ new URL(incoming.pathname + incoming.search, expected),
+ request,
+ );
+ normalized.headers.set(
+ "x-forwarded-proto",
+ new URL(expected).protocol.slice(0, -1),
+ );
+ normalized.headers.set("host", new URL(expected).host);
+ return normalized;
+}
diff --git a/app/middleware/design-studio.ts b/app/middleware/design-studio.ts
index c6175b7..a532734 100644
--- a/app/middleware/design-studio.ts
+++ b/app/middleware/design-studio.ts
@@ -2,6 +2,7 @@ import type { Middleware } from "@wrnexus/core";
import type { createStudioHandler } from "@wrnexus/design-studio/server";
import { studioAI } from "../lib/studio-ai.ts";
import { addStudioGuide, studioHelpScript } from "../lib/studio-guide.ts";
+import { studioRequest } from "../lib/studio-origin.ts";
let studio: ReturnType