diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000..002382503
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "submodules/chat"]
+ path = submodules/chat
+ url = https://github.com/lightfastai/chat.git
diff --git a/apps/www/next.config.ts b/apps/www/next.config.ts
index 81801dad1..e6e43b6c4 100644
--- a/apps/www/next.config.ts
+++ b/apps/www/next.config.ts
@@ -2,58 +2,58 @@ import { NextConfig } from "next";
import "~/env";
+import { mergeNextConfig } from "@vendor/next/merge-config";
import {
- config as vendorConfig,
- withBetterStack,
- withSentry,
+ config as vendorConfig,
+ withBetterStack,
+ withSentry,
} from "@vendor/next/next-config-builder";
-import { mergeNextConfig } from "@vendor/next/merge-config";
import { env } from "~/env";
let config: NextConfig = withBetterStack(
- mergeNextConfig(vendorConfig, {
- reactStrictMode: true,
-
- /** Enables hot reloading for local packages without a build step */
- transpilePackages: [
- "@repo/ui",
- "@vendor/security",
- "@vendor/analytics",
- "@vendor/email",
- "@vendor/clerk",
- "@vendor/inngest",
- "@vendor/observability",
- "@vendor/next",
- "@vendor/upstash",
- "@repo/lightfast-config",
- "@repo/lightfast-email",
- "@repo/lightfast-react",
- "@repo/lib",
- ],
-
- /** We already do linting and typechecking as separate tasks in CI */
- // eslint: { ignoreDuringBuilds: true },
- // typescript: { ignoreBuildErrors: true },
-
- // Add automatic static optimization where possible
- experimental: {
- // For Next.js 15.3+
- optimizeCss: true,
- optimizePackageImports: [
- "@repo/ui",
- "jotai",
- "lucide-react",
- "react-confetti",
- ],
- // Faster navigation for production
- // ppr: true,
- },
- })
+ mergeNextConfig(vendorConfig, {
+ reactStrictMode: true,
+
+ /** Enables hot reloading for local packages without a build step */
+ transpilePackages: [
+ "@repo/ui",
+ "@vendor/security",
+ "@vendor/analytics",
+ "@vendor/email",
+ "@vendor/clerk",
+ "@vendor/inngest",
+ "@vendor/observability",
+ "@vendor/next",
+ "@vendor/upstash",
+ "@repo/lightfast-config",
+ "@repo/lightfast-email",
+ "@repo/lightfast-react",
+ "@repo/lib",
+ ],
+
+ /** We already do linting and typechecking as separate tasks in CI */
+ // eslint: { ignoreDuringBuilds: true },
+ // typescript: { ignoreBuildErrors: true },
+
+ // Add automatic static optimization where possible
+ experimental: {
+ // For Next.js 15.3+
+ optimizeCss: true,
+ optimizePackageImports: [
+ "@repo/ui",
+ "jotai",
+ "lucide-react",
+ "react-confetti",
+ ],
+ // Faster navigation for production
+ // ppr: true,
+ },
+ }),
);
if (env.VERCEL) {
- config = withSentry(config);
+ config = withSentry(config);
}
export default config;
diff --git a/apps/www/src/app/(app)/(landing)/page.tsx b/apps/www/src/app/(app)/(landing)/page.tsx
index 94de46005..ea9932ef3 100644
--- a/apps/www/src/app/(app)/(landing)/page.tsx
+++ b/apps/www/src/app/(app)/(landing)/page.tsx
@@ -4,42 +4,42 @@ import { ZapIcon } from "lucide-react";
import { siteConfig } from "@repo/lightfast-config";
import { Icons } from "@repo/ui/components/icons";
-import { Button } from "@repo/ui/components/ui/button";
import { LightfastCustomGridBackground } from "@repo/ui/components/lightfast-custom-grid-background";
+import { Button } from "@repo/ui/components/ui/button";
export default function HomePage() {
- return (
-
-
-
-
-
- Crafting tomorrow's AI backbone with open-source infrastructure.
-
-
+ return (
+
+
+
+
+
+ Crafting tomorrow's AI backbone with open-source infrastructure.
+
+
-
-
-
+
+
+
-
-
-
-
-
-
- );
+
+
+
+
+
+
+ );
}
diff --git a/apps/www/src/app/(health)/api/health/route.ts b/apps/www/src/app/(health)/api/health/route.ts
index 24ac9675e..b0cabb9f6 100644
--- a/apps/www/src/app/(health)/api/health/route.ts
+++ b/apps/www/src/app/(health)/api/health/route.ts
@@ -1,16 +1,17 @@
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
+
import { env } from "~/env";
export const runtime = "edge";
/**
* Health check endpoint for monitoring services like BetterStack.
- *
+ *
* Authentication:
* - If HEALTH_CHECK_AUTH_TOKEN is set, requires Bearer token authentication
* - If not set, endpoint is public (for development/testing)
- *
+ *
* BetterStack Configuration:
* 1. Set HTTP method: GET
* 2. Add header: Authorization: Bearer
@@ -19,63 +20,58 @@ export const runtime = "edge";
export function GET(request: NextRequest) {
// Check if authentication is configured
const authToken = env.HEALTH_CHECK_AUTH_TOKEN;
-
+
if (authToken) {
// Extract bearer token from Authorization header
const authHeader = request.headers.get("authorization");
-
+
if (!authHeader) {
return NextResponse.json(
{ error: "Authorization required" },
- { status: 401 }
+ { status: 401 },
);
}
-
+
// Check for Bearer token format
const bearerRegex = /^Bearer\s+(.+)$/i;
const bearerMatch = bearerRegex.exec(authHeader);
if (!bearerMatch) {
return NextResponse.json(
{ error: "Invalid authorization format" },
- { status: 401 }
+ { status: 401 },
);
}
-
+
const providedToken = bearerMatch[1];
-
+
// Constant-time comparison to prevent timing attacks
const encoder = new TextEncoder();
const expectedBytes = encoder.encode(authToken);
const providedBytes = encoder.encode(providedToken);
-
+
if (expectedBytes.length !== providedBytes.length) {
- return NextResponse.json(
- { error: "Unauthorized" },
- { status: 401 }
- );
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
-
+
let valid = true;
for (let i = 0; i < expectedBytes.length; i++) {
if (expectedBytes[i] !== providedBytes[i]) {
valid = false;
}
}
-
+
if (!valid) {
- return NextResponse.json(
- { error: "Unauthorized" },
- { status: 401 }
- );
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
}
-
+
// Log monitoring access for observability (optional)
const userAgent = request.headers.get("user-agent") ?? "unknown";
- const isLikelyBetterStack = userAgent.toLowerCase().includes("betterstack") ||
- userAgent.toLowerCase().includes("uptime") ||
- userAgent.toLowerCase().includes("monitor");
-
+ const isLikelyBetterStack =
+ userAgent.toLowerCase().includes("betterstack") ||
+ userAgent.toLowerCase().includes("uptime") ||
+ userAgent.toLowerCase().includes("monitor");
+
// Return health status
const response = NextResponse.json(
{
@@ -100,12 +96,12 @@ export function GET(request: NextRequest) {
status: 200,
},
);
-
+
// Prevent caching of health check responses
response.headers.set("Cache-Control", "no-store, no-cache, must-revalidate");
response.headers.set("Pragma", "no-cache");
response.headers.set("Expires", "0");
-
+
return response;
}
@@ -116,7 +112,7 @@ export function OPTIONS() {
return new Response(null, {
status: 204,
headers: {
- "Allow": "GET, OPTIONS",
+ Allow: "GET, OPTIONS",
"Cache-Control": "no-store",
},
});
diff --git a/apps/www/src/app/global-error.tsx b/apps/www/src/app/global-error.tsx
index 2f8435f34..09918f126 100644
--- a/apps/www/src/app/global-error.tsx
+++ b/apps/www/src/app/global-error.tsx
@@ -7,10 +7,13 @@ import { captureException } from "@sentry/nextjs";
import { GeistMono } from "geist/font/mono";
import { GeistSans } from "geist/font/sans";
+import { LightfastCustomGridBackground } from "@repo/ui/components/lightfast-custom-grid-background";
+import {
+ ErrorCode,
+ LightfastErrorPage,
+} from "@repo/ui/components/lightfast-error-page";
import { Button } from "@repo/ui/components/ui/button";
import { cn } from "@repo/ui/lib/utils";
-import { LightfastCustomGridBackground } from "@repo/ui/components/lightfast-custom-grid-background";
-import { LightfastErrorPage, ErrorCode } from "@repo/ui/components/lightfast-error-page";
interface GlobalErrorProperties {
readonly error: NextError & { digest?: string };
diff --git a/apps/www/src/app/layout.tsx b/apps/www/src/app/layout.tsx
index 5e875869d..42d606545 100644
--- a/apps/www/src/app/layout.tsx
+++ b/apps/www/src/app/layout.tsx
@@ -12,108 +12,108 @@ import { SpeedInsights, VercelAnalytics } from "@vendor/analytics/vercel";
import { createBaseUrl } from "~/lib/base-url";
export const metadata: Metadata = {
- title: {
- default: siteConfig.name,
- template: `%s - ${siteConfig.name}`,
- },
- metadataBase: new URL(siteConfig.url),
- description: siteConfig.description,
- keywords: [
- "Lightfast",
- "AI",
- "Design",
- "Blender",
- "Unreal Engine",
- "Ableton",
- "MCP",
- "Model Context Protocol",
- "AI Copilot",
- "AI Copilot for Creatives",
- "AI Copilot for Blender",
- "AI Copilot for TouchDesigner",
- "AI Copilot for Houdini",
- "AI Copilot for Unreal Engine",
- ],
- authors: [
- {
- name: siteConfig.name,
- url: siteConfig.url,
- },
- ],
- creator: siteConfig.name,
- openGraph: {
- type: "website",
- locale: "en_US",
- url: siteConfig.url,
- title: siteConfig.name,
- description: siteConfig.description,
- siteName: siteConfig.name,
- images: [
- {
- url: siteConfig.ogImage,
- width: 1200,
- height: 630,
- alt: siteConfig.name,
- },
- ],
- },
- twitter: {
- card: "summary_large_image",
- title: siteConfig.name,
- description: siteConfig.description,
- images: [siteConfig.ogImage],
- creator: siteConfig.links.twitter.href,
- },
- icons: {
- icon: "/favicon.ico",
- shortcut: "/favicon-16x16.png",
- apple: "/apple-touch-icon.png",
- other: [
- {
- rel: "icon",
- url: "/favicon-32x32.png",
- },
- {
- rel: "icon",
- url: "/android-chrome-192x192.png",
- },
- {
- rel: "icon",
- url: "/android-chrome-512x512.png",
- },
- ],
- },
- applicationName: siteConfig.name,
- appleWebApp: {
- capable: true,
- statusBarStyle: "default",
- title: siteConfig.name,
- },
- formatDetection: {
- telephone: false,
- },
+ title: {
+ default: siteConfig.name,
+ template: `%s - ${siteConfig.name}`,
+ },
+ metadataBase: new URL(siteConfig.url),
+ description: siteConfig.description,
+ keywords: [
+ "Lightfast",
+ "AI",
+ "Design",
+ "Blender",
+ "Unreal Engine",
+ "Ableton",
+ "MCP",
+ "Model Context Protocol",
+ "AI Copilot",
+ "AI Copilot for Creatives",
+ "AI Copilot for Blender",
+ "AI Copilot for TouchDesigner",
+ "AI Copilot for Houdini",
+ "AI Copilot for Unreal Engine",
+ ],
+ authors: [
+ {
+ name: siteConfig.name,
+ url: siteConfig.url,
+ },
+ ],
+ creator: siteConfig.name,
+ openGraph: {
+ type: "website",
+ locale: "en_US",
+ url: siteConfig.url,
+ title: siteConfig.name,
+ description: siteConfig.description,
+ siteName: siteConfig.name,
+ images: [
+ {
+ url: siteConfig.ogImage,
+ width: 1200,
+ height: 630,
+ alt: siteConfig.name,
+ },
+ ],
+ },
+ twitter: {
+ card: "summary_large_image",
+ title: siteConfig.name,
+ description: siteConfig.description,
+ images: [siteConfig.ogImage],
+ creator: siteConfig.links.twitter.href,
+ },
+ icons: {
+ icon: "/favicon.ico",
+ shortcut: "/favicon-16x16.png",
+ apple: "/apple-touch-icon.png",
+ other: [
+ {
+ rel: "icon",
+ url: "/favicon-32x32.png",
+ },
+ {
+ rel: "icon",
+ url: "/android-chrome-192x192.png",
+ },
+ {
+ rel: "icon",
+ url: "/android-chrome-512x512.png",
+ },
+ ],
+ },
+ applicationName: siteConfig.name,
+ appleWebApp: {
+ capable: true,
+ statusBarStyle: "default",
+ title: siteConfig.name,
+ },
+ formatDetection: {
+ telephone: false,
+ },
};
export const viewport: Viewport = {
- themeColor: "#09090b",
+ themeColor: "#09090b",
};
export default function RootLayout({
- children,
+ children,
}: Readonly<{
- children: React.ReactNode;
+ children: React.ReactNode;
}>) {
- return (
-
-
-
-
- {children}
-
-
-
-
-
-
- );
+ return (
+
+
+
+ {children}
+
+
+
+
+
+
+ );
}
diff --git a/apps/www/src/app/not-found.tsx b/apps/www/src/app/not-found.tsx
index 623b38de0..4e2e1dcb5 100644
--- a/apps/www/src/app/not-found.tsx
+++ b/apps/www/src/app/not-found.tsx
@@ -1,26 +1,30 @@
import Link from "next/link";
-import { Button } from "@repo/ui/components/ui/button";
+
import { LightfastCustomGridBackground } from "@repo/ui/components/lightfast-custom-grid-background";
-import { LightfastErrorPage, ErrorCode } from "@repo/ui/components/lightfast-error-page";
+import {
+ ErrorCode,
+ LightfastErrorPage,
+} from "@repo/ui/components/lightfast-error-page";
+import { Button } from "@repo/ui/components/ui/button";
export default function NotFound() {
- return (
-
-
-
-
-
-
-
- );
+ return (
+
+
+
+
+
+
+
+ );
}
diff --git a/apps/www/src/components/early-access/early-access-form.tsx b/apps/www/src/components/early-access/early-access-form.tsx
index 07ee4e050..944fb7fd2 100644
--- a/apps/www/src/components/early-access/early-access-form.tsx
+++ b/apps/www/src/components/early-access/early-access-form.tsx
@@ -2,13 +2,12 @@
import type * as z from "zod";
import { useState } from "react";
+import { zodResolver } from "@hookform/resolvers/zod";
import { useAtom } from "jotai";
import { Send } from "lucide-react";
import Confetti from "react-confetti";
import { createPortal } from "react-dom";
-
import { useForm } from "react-hook-form";
-import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@repo/ui/components/ui/button";
import {
diff --git a/apps/www/src/components/site-footer.tsx b/apps/www/src/components/site-footer.tsx
index 968eff695..eca96d7ee 100644
--- a/apps/www/src/components/site-footer.tsx
+++ b/apps/www/src/components/site-footer.tsx
@@ -1,9 +1,10 @@
-import { cn } from "@repo/ui/lib/utils"
-import { Dot } from "lucide-react"
-import Link from "next/link"
+import Link from "next/link";
+import { Dot } from "lucide-react";
+
+import { cn } from "@repo/ui/lib/utils";
interface FooterProps {
- className?: string
+ className?: string;
}
export function SiteFooter({ className }: FooterProps) {
@@ -112,5 +113,5 @@ export function SiteFooter({ className }: FooterProps) {
- )
+ );
}
diff --git a/apps/www/src/middleware.ts b/apps/www/src/middleware.ts
index d9508027b..80f87adce 100644
--- a/apps/www/src/middleware.ts
+++ b/apps/www/src/middleware.ts
@@ -2,8 +2,8 @@ import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
-import { log } from "@vendor/observability/log";
import { getAllAppUrls, getClerkMiddlewareConfig } from "@repo/url-utils";
+import { log } from "@vendor/observability/log";
import {
generateSignedRequestId,
@@ -15,48 +15,51 @@ const clerkConfig = getClerkMiddlewareConfig("www");
// Define public routes that don't require authentication
const isPublicRoute = createRouteMatcher([
"/",
- "/api/health",
+ "/api/health",
"/api/early-access/(.*)",
- "/legal/(.*)"
+ "/legal/(.*)",
]);
/**
* Middleware to handle request ID generation, authentication, and protected routes
*/
-export const middleware = clerkMiddleware(async (auth, request: NextRequest) => {
- // Handle authentication protection first
- if (!isPublicRoute(request)) {
- await auth.protect();
- }
-
- const { userId } = await auth();
-
- // If user is authenticated and on landing page, redirect to app
- if (userId && request.nextUrl.pathname === "/") {
- const urls = getAllAppUrls();
- return NextResponse.redirect(new URL(urls.app));
- }
+export const middleware = clerkMiddleware(
+ async (auth, request: NextRequest) => {
+ // Handle authentication protection first
+ if (!isPublicRoute(request)) {
+ await auth.protect();
+ }
+
+ const { userId } = await auth();
+
+ // If user is authenticated and on landing page, redirect to app
+ if (userId && request.nextUrl.pathname === "/") {
+ const urls = getAllAppUrls();
+ return NextResponse.redirect(new URL(urls.app));
+ }
- // Generate a new request ID for all requests if one doesn't exist
- const existingRequestId = request.headers.get(REQUEST_ID_HEADER);
- const requestId = existingRequestId ?? (await generateSignedRequestId(log));
+ // Generate a new request ID for all requests if one doesn't exist
+ const existingRequestId = request.headers.get(REQUEST_ID_HEADER);
+ const requestId = existingRequestId ?? (await generateSignedRequestId(log));
- // Clone the request headers and set the request ID
- const requestHeaders = new Headers(request.headers);
- requestHeaders.set(REQUEST_ID_HEADER, requestId);
+ // Clone the request headers and set the request ID
+ const requestHeaders = new Headers(request.headers);
+ requestHeaders.set(REQUEST_ID_HEADER, requestId);
- // Return response with both request and response headers set
- return NextResponse.next({
- request: {
- // New request headers
- headers: requestHeaders,
- },
- headers: {
- // Also set response header for consistency
- [REQUEST_ID_HEADER]: requestId,
- },
- });
-}, clerkConfig);
+ // Return response with both request and response headers set
+ return NextResponse.next({
+ request: {
+ // New request headers
+ headers: requestHeaders,
+ },
+ headers: {
+ // Also set response header for consistency
+ [REQUEST_ID_HEADER]: requestId,
+ },
+ });
+ },
+ clerkConfig,
+);
export const config = {
matcher: [
@@ -65,4 +68,4 @@ export const config = {
// Always run for API routes except Inngest
"/(api(?!/inngest)|trpc)(.*)",
],
-}
+};
diff --git a/submodules/chat b/submodules/chat
new file mode 160000
index 000000000..6ebdd4b58
--- /dev/null
+++ b/submodules/chat
@@ -0,0 +1 @@
+Subproject commit 6ebdd4b58a303183f757b8874b5bf56afb893e90