diff --git a/.gitignore b/.gitignore index 965ef504ae..0dca1aacab 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ dist-ssr *.vars .wrangler _worker.bundle + +.dev.vars diff --git a/app/entry.client.tsx b/app/entry.client.tsx index 62917e70d4..16d03b5932 100644 --- a/app/entry.client.tsx +++ b/app/entry.client.tsx @@ -1,7 +1,9 @@ import { RemixBrowser } from '@remix-run/react'; import { startTransition } from 'react'; import { hydrateRoot } from 'react-dom/client'; +import "@unocss/reset/tailwind.css"; +import "virtual:uno.css"; startTransition(() => { hydrateRoot(document.getElementById('root')!, ); -}); +}); \ No newline at end of file diff --git a/app/entry.server.tsx b/app/entry.server.tsx index 4baf07001d..2faed2ebee 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -1,11 +1,8 @@ import type { AppLoadContext, EntryContext } from '@remix-run/cloudflare'; import { RemixServer } from '@remix-run/react'; import { isbot } from 'isbot'; -import { renderToReadableStream } from 'react-dom/server'; -import { renderHeadToString } from 'remix-island'; -import { Head } from './root'; -import { themeStore } from '~/lib/stores/theme'; - +import ReactDOMServer from 'react-dom/server'; +import * as ReactDOMServerBrowser from 'react-dom/server.browser'; export default async function handleRequest( request: Request, responseStatusCode: number, @@ -13,54 +10,25 @@ export default async function handleRequest( remixContext: EntryContext, _loadContext: AppLoadContext, ) { - const readable = await renderToReadableStream(, { - signal: request.signal, - onError(error: unknown) { - console.error(error); - responseStatusCode = 500; - }, - }); - - const body = new ReadableStream({ - start(controller) { - const head = renderHeadToString({ request, remixContext, Head }); - - controller.enqueue( - new Uint8Array( - new TextEncoder().encode( - `${head}
`, - ), - ), - ); - - const reader = readable.getReader(); - - function read() { - reader - .read() - .then(({ done, value }) => { - if (done) { - controller.enqueue(new Uint8Array(new TextEncoder().encode(`
`))); - controller.close(); - - return; - } - - controller.enqueue(value); - read(); - }) - .catch((error) => { - controller.error(error); - readable.cancel(); - }); - } - read(); + const serverExports = ReactDOMServer as typeof import('react-dom/server'); + const browserServerExports = + ReactDOMServerBrowser as typeof import('react-dom/server.browser'); + + const renderToReadableStream = + typeof serverExports.renderToReadableStream === 'function' + ? serverExports.renderToReadableStream + : browserServerExports.renderToReadableStream; + + const readable = await renderToReadableStream( + , + { + signal: request.signal, + onError(error: unknown) { + console.error(error); + responseStatusCode = 500; + }, }, - - cancel() { - readable.cancel(); - }, - }); + ); if (isbot(request.headers.get('user-agent') || '')) { await readable.allReady; @@ -71,7 +39,7 @@ export default async function handleRequest( responseHeaders.set('Cross-Origin-Embedder-Policy', 'require-corp'); responseHeaders.set('Cross-Origin-Opener-Policy', 'same-origin'); - return new Response(body, { + return new Response(readable, { headers: responseHeaders, status: responseStatusCode, }); diff --git a/app/lib/.server/llm/stream-text.ts b/app/lib/.server/llm/stream-text.ts index cf937fd00e..8433e19826 100644 --- a/app/lib/.server/llm/stream-text.ts +++ b/app/lib/.server/llm/stream-text.ts @@ -12,7 +12,7 @@ interface ToolResult { } interface Message { - role: 'user' | 'assistant'; + role: 'system' | 'user' | 'assistant'; content: string; toolInvocations?: ToolResult[]; } diff --git a/app/lib/runtime/action-runner.ts b/app/lib/runtime/action-runner.ts index e2ea6a2264..99c143b4b6 100644 --- a/app/lib/runtime/action-runner.ts +++ b/app/lib/runtime/action-runner.ts @@ -1,6 +1,6 @@ import { WebContainer } from '@webcontainer/api'; import { map, type MapStore } from 'nanostores'; -import * as nodePath from 'node:path'; +import * as nodePath from 'path'; import type { BoltAction } from '~/types/actions'; import { createScopedLogger } from '~/utils/logger'; import { unreachable } from '~/utils/unreachable'; @@ -184,3 +184,4 @@ export class ActionRunner { this.actions.setKey(id, { ...actions[id], ...newState }); } } + diff --git a/app/lib/stores/files.ts b/app/lib/stores/files.ts index 663ae58113..f9754511d2 100644 --- a/app/lib/stores/files.ts +++ b/app/lib/stores/files.ts @@ -1,8 +1,8 @@ import type { PathWatcherEvent, WebContainer } from '@webcontainer/api'; import { getEncoding } from 'istextorbinary'; import { map, type MapStore } from 'nanostores'; -import { Buffer } from 'node:buffer'; -import * as nodePath from 'node:path'; +import { Buffer } from 'buffer'; +import * as nodePath from 'path'; import { bufferWatchEvents } from '~/utils/buffer'; import { WORK_DIR } from '~/utils/constants'; import { computeFileModifications } from '~/utils/diff'; @@ -218,3 +218,4 @@ function convertToBuffer(view: Uint8Array): Buffer { return buffer as Buffer; } + diff --git a/app/remix-server-build.d.tsx b/app/remix-server-build.d.tsx new file mode 100644 index 0000000000..e2aa1eb445 --- /dev/null +++ b/app/remix-server-build.d.tsx @@ -0,0 +1,6 @@ +declare module "../build/server/index.js" { + // Good enough for editor; Remix will provide the real thing at runtime + // You can refine this to ServerBuild if you want stricter types. + const build: any; + export = build; +} diff --git a/app/root.tsx b/app/root.tsx index 31eb387e03..157c456980 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -1,83 +1,79 @@ -import { useStore } from '@nanostores/react'; -import type { LinksFunction } from '@remix-run/cloudflare'; -import { Links, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react'; -import tailwindReset from '@unocss/reset/tailwind-compat.css?url'; -import { themeStore } from './lib/stores/theme'; -import { stripIndents } from './utils/stripIndent'; -import { createHead } from 'remix-island'; -import { useEffect } from 'react'; +// app/root.tsx +import type { LinksFunction } from "@remix-run/cloudflare"; +import { + Links, + Meta, + Outlet, + Scripts, + ScrollRestoration, + isRouteErrorResponse, + useRouteError, +} from "@remix-run/react"; +import type { ReactNode } from "react"; -import reactToastifyStyles from 'react-toastify/dist/ReactToastify.css?url'; -import globalStyles from './styles/index.scss?url'; -import xtermStyles from '@xterm/xterm/css/xterm.css?url'; +// Side-effect CSS imports (keep these; no ?url) +import "@unocss/reset/tailwind.css"; +import "virtual:uno.css"; -import 'virtual:uno.css'; +import { DEFAULT_THEME, kTheme } from "~/lib/stores/theme"; + +const themeInitScript = `(() => { + try { + const storedTheme = localStorage.getItem(${JSON.stringify(kTheme)}); + if (storedTheme) { + document.documentElement.setAttribute('data-theme', storedTheme); + } + } catch {} +})();`; export const links: LinksFunction = () => [ - { - rel: 'icon', - href: '/favicon.svg', - type: 'image/svg+xml', - }, - { rel: 'stylesheet', href: reactToastifyStyles }, - { rel: 'stylesheet', href: tailwindReset }, - { rel: 'stylesheet', href: globalStyles }, - { rel: 'stylesheet', href: xtermStyles }, - { - rel: 'preconnect', - href: 'https://fonts.googleapis.com', - }, - { - rel: 'preconnect', - href: 'https://fonts.gstatic.com', - crossOrigin: 'anonymous', - }, - { - rel: 'stylesheet', - href: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap', - }, + { rel: "icon", href: "/favicon.svg", type: "image/svg+xml" }, ]; -const inlineThemeCode = stripIndents` - setTutorialKitTheme(); +// 👇 This is what entry.server.tsx expects to exist +export default function App() { + return ( + + + + ); +} - function setTutorialKitTheme() { - let theme = localStorage.getItem('bolt_theme'); +export function Layout({ children }: { children: ReactNode }) { + return ( + + + + +