+ {/* Editor Panel */}
- {
- if (message.role === 'user') {
- return message;
- }
- return {
- ...message,
- content: parsedMessages[i] || '',
- };
- })}
- enhancePrompt={() => {
- enhancePrompt(input, (input) => {
- setInput(input);
- scrollTextArea();
- });
- }}
+
- {/* Editor and Preview Panel */}
-
- {/* Editor Panel */}
-
-
-
-
- {/* Preview Panel */}
-
+ {/* Preview Panel */}
+
);
diff --git a/app/routes/challenges.$id.tsx b/app/routes/challenges.$id.tsx
index 4ae40ba75e..b97fca7a29 100644
--- a/app/routes/challenges.$id.tsx
+++ b/app/routes/challenges.$id.tsx
@@ -39,9 +39,37 @@ export default function ChallengePage() {
return (
-
}>
- {() =>
}
-
+
+ {/* Challenge Description Panel */}
+
+
+
+
+ {challenge.title}
+
+
+ Challenge ID: {challenge.id}
+
+
+
+
+
+ Problem Statement
+
+
+ {challenge.question}
+
+
+
+
+
+ {/* Editor and Preview Area */}
+
+ }>
+ {() => }
+
+
+
);
}
\ No newline at end of file
From 43f6dd2a4dc358c2e91c5a7bc4c366742b605976 Mon Sep 17 00:00:00 2001
From: Jad El Asmar <42979241+1elasmarjad@users.noreply.github.com>
Date: Sat, 13 Sep 2025 10:49:04 -0400
Subject: [PATCH 04/14] Revert "Created /challenges route"
This reverts commit c6ff697b24531127599531c1fd8c8ff851356106.
---
.../challenge/BaseChallengeWorkbench.tsx | 21 --
.../challenge/ChallengeWorkbench.client.tsx | 211 ------------------
app/lib/challenges.ts | 28 ---
app/routes/challenges.$id.tsx | 75 -------
data/challenges.json | 12 -
5 files changed, 347 deletions(-)
delete mode 100644 app/components/challenge/BaseChallengeWorkbench.tsx
delete mode 100644 app/components/challenge/ChallengeWorkbench.client.tsx
delete mode 100644 app/lib/challenges.ts
delete mode 100644 app/routes/challenges.$id.tsx
delete mode 100644 data/challenges.json
diff --git a/app/components/challenge/BaseChallengeWorkbench.tsx b/app/components/challenge/BaseChallengeWorkbench.tsx
deleted file mode 100644
index d9831b6de3..0000000000
--- a/app/components/challenge/BaseChallengeWorkbench.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { memo } from 'react';
-
-export const BaseChallengeWorkbench = memo(() => {
- return (
-
- {/* Editor Panel Placeholder */}
-
-
- {/* Preview Panel Placeholder */}
-
-
- );
-});
\ No newline at end of file
diff --git a/app/components/challenge/ChallengeWorkbench.client.tsx b/app/components/challenge/ChallengeWorkbench.client.tsx
deleted file mode 100644
index 49718eb703..0000000000
--- a/app/components/challenge/ChallengeWorkbench.client.tsx
+++ /dev/null
@@ -1,211 +0,0 @@
-import { useStore } from '@nanostores/react';
-import { memo, useCallback, useEffect, useMemo } from 'react';
-import { toast } from 'react-toastify';
-import {
- type OnChangeCallback as OnEditorChange,
- type OnScrollCallback as OnEditorScroll,
-} from '~/components/editor/codemirror/CodeMirrorEditor';
-import { workbenchStore } from '~/lib/stores/workbench';
-import { renderLogger } from '~/utils/logger';
-import { EditorPanel } from '../workbench/EditorPanel';
-import { Preview } from '../workbench/Preview';
-
-const CHALLENGE_SCAFFOLD = {
- 'src/App.tsx': `import { useState } from 'react';
-import './App.css';
-
-function App() {
- return (
-
-
Challenge
-
Start building your solution here!
-
- );
-}
-
-export default App;`,
-
- 'src/App.css': `.App {
- text-align: center;
- padding: 2rem;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
- 'Ubuntu', 'Cantarell', 'Open Sans', 'Helvetica Neue', sans-serif;
-}
-
-.App h1 {
- color: #333;
- margin-bottom: 1rem;
-}`,
-
- 'src/main.tsx': `import React from 'react';
-import ReactDOM from 'react-dom/client';
-import App from './App';
-
-ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
-
-
-
-);`,
-
- 'index.html': `
-
-
-
-
-
-
Challenge
-
-
-
-
-
-`,
-
- 'package.json': `{
- "name": "challenge",
- "private": true,
- "version": "0.0.0",
- "type": "module",
- "scripts": {
- "dev": "vite",
- "build": "tsc && vite build",
- "preview": "vite preview"
- },
- "dependencies": {
- "react": "^18.2.0",
- "react-dom": "^18.2.0"
- },
- "devDependencies": {
- "@types/react": "^18.2.15",
- "@types/react-dom": "^18.2.7",
- "@vitejs/plugin-react": "^4.0.3",
- "typescript": "^5.0.2",
- "vite": "^4.4.5"
- }
-}`,
-
- 'vite.config.ts': `import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
-
-export default defineConfig({
- plugins: [react()],
-})`,
-
- 'tsconfig.json': `{
- "compilerOptions": {
- "target": "ES2020",
- "useDefineForClassFields": true,
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
- "module": "ESNext",
- "skipLibCheck": true,
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "jsx": "react-jsx",
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true
- },
- "include": ["src"],
- "references": [{ "path": "./tsconfig.node.json" }]
-}`,
-
- 'tsconfig.node.json': `{
- "compilerOptions": {
- "composite": true,
- "skipLibCheck": true,
- "module": "ESNext",
- "moduleResolution": "bundler",
- "allowSyntheticDefaultImports": true
- },
- "include": ["vite.config.ts"]
-}`
-};
-
-export const ChallengeWorkbench = memo(() => {
- renderLogger.trace('ChallengeWorkbench');
-
- const currentDocument = useStore(workbenchStore.currentDocument);
- const unsavedFiles = useStore(workbenchStore.unsavedFiles);
- const files = useStore(workbenchStore.files);
- const selectedFile = useStore(workbenchStore.selectedFile);
-
- // Initialize the challenge scaffold when component mounts
- useEffect(() => {
- const initializeScaffold = async () => {
- try {
- // Set up the basic scaffold files
- const mockFiles = Object.entries(CHALLENGE_SCAFFOLD).reduce((acc, [path, content]) => {
- acc[path] = {
- type: 'file' as const,
- content,
- };
- return acc;
- }, {} as any);
-
- workbenchStore.setDocuments(mockFiles);
- workbenchStore.setShowWorkbench(true);
-
- // Select App.tsx by default
- workbenchStore.setSelectedFile('src/App.tsx');
- } catch (error) {
- console.error('Failed to initialize challenge scaffold:', error);
- toast.error('Failed to initialize challenge environment');
- }
- };
-
- if (Object.keys(files).length === 0) {
- initializeScaffold();
- }
- }, [files]);
-
- const onEditorChange = useCallback
((update) => {
- workbenchStore.setCurrentDocumentContent(update.content);
- }, []);
-
- const onEditorScroll = useCallback((position) => {
- workbenchStore.setCurrentDocumentScrollPosition(position);
- }, []);
-
- const onFileSelect = useCallback((filePath: string | undefined) => {
- workbenchStore.setSelectedFile(filePath);
- }, []);
-
- const onFileSave = useCallback(() => {
- workbenchStore.saveCurrentDocument().catch(() => {
- toast.error('Failed to update file content');
- });
- }, []);
-
- const onFileReset = useCallback(() => {
- workbenchStore.resetCurrentDocument();
- }, []);
-
- return (
-
- {/* Editor Panel */}
-
-
-
-
- {/* Preview Panel */}
-
-
- );
-});
\ No newline at end of file
diff --git a/app/lib/challenges.ts b/app/lib/challenges.ts
deleted file mode 100644
index ffddf32bde..0000000000
--- a/app/lib/challenges.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-export type Challenge = {
- id: string;
- title: string;
- question: string;
-};
-
-// Inline challenge data to avoid JSON import issues
-const challengesData: Challenge[] = [
- {
- "id": "counter",
- "title": "Build a Counter",
- "question": "Make a click counter with +, -, and reset buttons. The value must never go below 0."
- },
- {
- "id": "todo",
- "title": "Todo List",
- "question": "Build a todo list with add and delete functionality."
- }
-];
-
-export function loadChallenge(id: string): Challenge | null {
- const challenge = challengesData.find((c) => c.id === id);
- return challenge || null;
-}
-
-export function getAllChallenges(): Challenge[] {
- return challengesData;
-}
\ No newline at end of file
diff --git a/app/routes/challenges.$id.tsx b/app/routes/challenges.$id.tsx
deleted file mode 100644
index b97fca7a29..0000000000
--- a/app/routes/challenges.$id.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import { json, type LoaderFunctionArgs, type MetaFunction } from '@remix-run/cloudflare';
-import { useLoaderData } from '@remix-run/react';
-import { ClientOnly } from 'remix-utils/client-only';
-import { Header } from '~/components/header/Header';
-import { loadChallenge, type Challenge } from '~/lib/challenges';
-import { ChallengeWorkbench } from '~/components/challenge/ChallengeWorkbench.client';
-import { BaseChallengeWorkbench } from '~/components/challenge/BaseChallengeWorkbench';
-
-export const meta: MetaFunction = ({ data }) => {
- if (!data?.challenge) {
- return [{ title: 'Challenge Not Found - Kleos Frontend' }];
- }
-
- return [
- { title: `${data.challenge.title} - Kleos Frontend` },
- { name: 'description', content: data.challenge.question }
- ];
-};
-
-export async function loader({ params }: LoaderFunctionArgs) {
- const challengeId = params.id;
-
- if (!challengeId) {
- throw new Response('Not Found', { status: 404 });
- }
-
- const challenge = loadChallenge(challengeId);
-
- if (!challenge) {
- throw new Response('Not Found', { status: 404 });
- }
-
- return json({ challenge });
-}
-
-export default function ChallengePage() {
- const { challenge } = useLoaderData();
-
- return (
-
-
-
- {/* Challenge Description Panel */}
-
-
-
-
- {challenge.title}
-
-
- Challenge ID: {challenge.id}
-
-
-
-
-
- Problem Statement
-
-
- {challenge.question}
-
-
-
-
-
- {/* Editor and Preview Area */}
-
- }>
- {() => }
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/data/challenges.json b/data/challenges.json
deleted file mode 100644
index 99d82fc03d..0000000000
--- a/data/challenges.json
+++ /dev/null
@@ -1,12 +0,0 @@
-[
- {
- "id": "counter",
- "title": "Build a Counter",
- "question": "Make a click counter with +, -, and reset buttons. The value must never go below 0."
- },
- {
- "id": "todo",
- "title": "Todo List",
- "question": "Build a todo list with add and delete functionality."
- }
-]
\ No newline at end of file
From 703ac6df6c6903597bdf8fb5fb03814a85d713cb Mon Sep 17 00:00:00 2001
From: azamjb
Date: Sat, 13 Sep 2025 11:32:28 -0400
Subject: [PATCH 05/14] color
---
app/components/header/Header.tsx | 5 +-
app/styles/variables.scss | 293 ++++++++-----------------------
2 files changed, 75 insertions(+), 223 deletions(-)
diff --git a/app/components/header/Header.tsx b/app/components/header/Header.tsx
index 15cf4bfbd0..355b309207 100644
--- a/app/components/header/Header.tsx
+++ b/app/components/header/Header.tsx
@@ -19,10 +19,7 @@ export function Header() {
)}
>
-
-
-
-
+ {/* Removed bolt logo and sidebar icon */}
{() => }
diff --git a/app/styles/variables.scss b/app/styles/variables.scss
index 38967b1171..758406d4fa 100644
--- a/app/styles/variables.scss
+++ b/app/styles/variables.scss
@@ -1,251 +1,106 @@
-/* Color Tokens Light Theme */
+/* Color Tokens Dark Elegant Theme (Purple Accent) */
:root,
:root[data-theme='light'] {
- --bolt-elements-borderColor: theme('colors.alpha.gray.10');
- --bolt-elements-borderColorActive: theme('colors.accent.600');
+ --bolt-elements-borderColor: #23263a; /* deep blue-gray border */
+ --bolt-elements-borderColorActive: #7c3aed; /* purple accent */
- --bolt-elements-bg-depth-1: theme('colors.white');
- --bolt-elements-bg-depth-2: theme('colors.gray.50');
- --bolt-elements-bg-depth-3: theme('colors.gray.200');
- --bolt-elements-bg-depth-4: theme('colors.alpha.gray.5');
+ --bolt-elements-bg-depth-1: #181a20; /* dark background */
+ --bolt-elements-bg-depth-2: #23263a;
+ --bolt-elements-bg-depth-3: #23263a;
+ --bolt-elements-bg-depth-4: #23263a;
- --bolt-elements-textPrimary: theme('colors.gray.950');
- --bolt-elements-textSecondary: theme('colors.gray.600');
- --bolt-elements-textTertiary: theme('colors.gray.500');
+ --bolt-elements-textPrimary: #f3f4f6; /* near white */
+ --bolt-elements-textSecondary: #a1a1aa; /* muted gray */
+ --bolt-elements-textTertiary: #7c3aed; /* purple accent */
- --bolt-elements-code-background: theme('colors.gray.100');
- --bolt-elements-code-text: theme('colors.gray.950');
+ --bolt-elements-code-background: #23263a;
+ --bolt-elements-code-text: #e0e7ef;
- --bolt-elements-button-primary-background: theme('colors.alpha.accent.10');
- --bolt-elements-button-primary-backgroundHover: theme('colors.alpha.accent.20');
- --bolt-elements-button-primary-text: theme('colors.accent.500');
+ --bolt-elements-button-primary-background: #7c3aed;
+ --bolt-elements-button-primary-backgroundHover: #a78bfa;
+ --bolt-elements-button-primary-text: #fff;
- --bolt-elements-button-secondary-background: theme('colors.alpha.gray.5');
- --bolt-elements-button-secondary-backgroundHover: theme('colors.alpha.gray.10');
- --bolt-elements-button-secondary-text: theme('colors.gray.950');
+ --bolt-elements-button-secondary-background: #23263a;
+ --bolt-elements-button-secondary-backgroundHover: #2d3142;
+ --bolt-elements-button-secondary-text: #f3f4f6;
- --bolt-elements-button-danger-background: theme('colors.alpha.red.10');
- --bolt-elements-button-danger-backgroundHover: theme('colors.alpha.red.20');
- --bolt-elements-button-danger-text: theme('colors.red.500');
+ --bolt-elements-button-danger-background: #7c223a;
+ --bolt-elements-button-danger-backgroundHover: #a8324a;
+ --bolt-elements-button-danger-text: #ffb4d4;
- --bolt-elements-item-contentDefault: theme('colors.alpha.gray.50');
- --bolt-elements-item-contentActive: theme('colors.gray.950');
- --bolt-elements-item-contentAccent: theme('colors.accent.700');
- --bolt-elements-item-contentDanger: theme('colors.red.500');
+ --bolt-elements-item-contentDefault: #a1a1aa;
+ --bolt-elements-item-contentActive: #f3f4f6;
+ --bolt-elements-item-contentAccent: #7c3aed;
+ --bolt-elements-item-contentDanger: #ffb4d4;
--bolt-elements-item-backgroundDefault: rgba(0, 0, 0, 0);
- --bolt-elements-item-backgroundActive: theme('colors.alpha.gray.5');
- --bolt-elements-item-backgroundAccent: theme('colors.alpha.accent.10');
- --bolt-elements-item-backgroundDanger: theme('colors.alpha.red.10');
+ --bolt-elements-item-backgroundActive: #23263a;
+ --bolt-elements-item-backgroundAccent: #23263a;
+ --bolt-elements-item-backgroundDanger: #7c223a;
- --bolt-elements-loader-background: theme('colors.alpha.gray.10');
- --bolt-elements-loader-progress: theme('colors.accent.500');
+ --bolt-elements-loader-background: #23263a;
+ --bolt-elements-loader-progress: #7c3aed;
- --bolt-elements-artifacts-background: theme('colors.white');
- --bolt-elements-artifacts-backgroundHover: theme('colors.alpha.gray.2');
+ --bolt-elements-artifacts-background: #23263a;
+ --bolt-elements-artifacts-backgroundHover: #2d3142;
--bolt-elements-artifacts-borderColor: var(--bolt-elements-borderColor);
- --bolt-elements-artifacts-inlineCode-background: theme('colors.gray.100');
+ --bolt-elements-artifacts-inlineCode-background: #23263a;
--bolt-elements-artifacts-inlineCode-text: var(--bolt-elements-textPrimary);
- --bolt-elements-actions-background: theme('colors.white');
- --bolt-elements-actions-code-background: theme('colors.gray.800');
+ --bolt-elements-actions-background: #181a20;
+ --bolt-elements-actions-code-background: #23263a;
- --bolt-elements-messages-background: theme('colors.gray.100');
- --bolt-elements-messages-linkColor: theme('colors.accent.500');
- --bolt-elements-messages-code-background: theme('colors.gray.800');
- --bolt-elements-messages-inlineCode-background: theme('colors.gray.200');
- --bolt-elements-messages-inlineCode-text: theme('colors.gray.800');
+ --bolt-elements-messages-background: #23263a;
+ --bolt-elements-messages-linkColor: #a78bfa;
+ --bolt-elements-messages-code-background: #2d3142;
+ --bolt-elements-messages-inlineCode-background: #2d3142;
+ --bolt-elements-messages-inlineCode-text: #f3f4f6;
- --bolt-elements-icon-success: theme('colors.green.500');
- --bolt-elements-icon-error: theme('colors.red.500');
- --bolt-elements-icon-primary: theme('colors.gray.950');
- --bolt-elements-icon-secondary: theme('colors.gray.600');
- --bolt-elements-icon-tertiary: theme('colors.gray.500');
+ --bolt-elements-icon-success: #22c55e;
+ --bolt-elements-icon-error: #ef4444;
+ --bolt-elements-icon-primary: #f3f4f6;
+ --bolt-elements-icon-secondary: #a1a1aa;
+ --bolt-elements-icon-tertiary: #7c3aed;
- --bolt-elements-dividerColor: theme('colors.gray.100');
+ --bolt-elements-dividerColor: #23263a;
- --bolt-elements-prompt-background: theme('colors.alpha.white.80');
+ --bolt-elements-prompt-background: #23263acc;
- --bolt-elements-sidebar-dropdownShadow: theme('colors.alpha.gray.10');
- --bolt-elements-sidebar-buttonBackgroundDefault: theme('colors.alpha.accent.10');
- --bolt-elements-sidebar-buttonBackgroundHover: theme('colors.alpha.accent.20');
- --bolt-elements-sidebar-buttonText: theme('colors.accent.700');
+ --bolt-elements-sidebar-dropdownShadow: #23263a;
+ --bolt-elements-sidebar-buttonBackgroundDefault: #23263a;
+ --bolt-elements-sidebar-buttonBackgroundHover: #2d3142;
+ --bolt-elements-sidebar-buttonText: #a78bfa;
- --bolt-elements-preview-addressBar-background: theme('colors.gray.100');
- --bolt-elements-preview-addressBar-backgroundHover: theme('colors.alpha.gray.5');
- --bolt-elements-preview-addressBar-backgroundActive: theme('colors.white');
+ --bolt-elements-preview-addressBar-background: #23263a;
+ --bolt-elements-preview-addressBar-backgroundHover: #2d3142;
+ --bolt-elements-preview-addressBar-backgroundActive: #2d3142;
--bolt-elements-preview-addressBar-text: var(--bolt-elements-textSecondary);
--bolt-elements-preview-addressBar-textActive: var(--bolt-elements-textPrimary);
- --bolt-elements-terminals-background: theme('colors.white');
+ --bolt-elements-terminals-background: #181a20;
--bolt-elements-terminals-buttonBackground: var(--bolt-elements-bg-depth-4);
- --bolt-elements-cta-background: theme('colors.gray.100');
- --bolt-elements-cta-text: theme('colors.gray.950');
+ --bolt-elements-cta-background: #23263a;
+ --bolt-elements-cta-text: #f3f4f6;
/* Terminal Colors */
--bolt-terminal-background: var(--bolt-elements-terminals-background);
- --bolt-terminal-foreground: #333333;
- --bolt-terminal-selection-background: #00000040;
+ --bolt-terminal-foreground: #f3f4f6;
+ --bolt-terminal-selection-background: #7c3aed33;
--bolt-terminal-black: #000000;
- --bolt-terminal-red: #cd3131;
- --bolt-terminal-green: #00bc00;
- --bolt-terminal-yellow: #949800;
- --bolt-terminal-blue: #0451a5;
- --bolt-terminal-magenta: #bc05bc;
- --bolt-terminal-cyan: #0598bc;
- --bolt-terminal-white: #555555;
- --bolt-terminal-brightBlack: #686868;
- --bolt-terminal-brightRed: #cd3131;
- --bolt-terminal-brightGreen: #00bc00;
- --bolt-terminal-brightYellow: #949800;
- --bolt-terminal-brightBlue: #0451a5;
- --bolt-terminal-brightMagenta: #bc05bc;
- --bolt-terminal-brightCyan: #0598bc;
- --bolt-terminal-brightWhite: #a5a5a5;
+ --bolt-terminal-red: #ef4444;
+ --bolt-terminal-green: #22c55e;
+ --bolt-terminal-yellow: #eab308;
+ --bolt-terminal-blue: #7c3aed;
+ --bolt-terminal-magenta: #a855f7;
+ --bolt-terminal-cyan: #06b6d4;
+ --bolt-terminal-white: #f3f4f6;
+ --bolt-terminal-brightBlack: #a1a1aa;
+ --bolt-terminal-brightRed: #ef4444;
+ --bolt-terminal-brightGreen: #22c55e;
+ --bolt-terminal-brightYellow: #eab308;
+ --bolt-terminal-brightBlue: #a78bfa;
+ --bolt-terminal-brightMagenta: #a78bfa;
+ --bolt-terminal-brightCyan: #06b6d4;
+ --bolt-terminal-brightWhite: #ffffff;
}
-/* Color Tokens Dark Theme */
-:root,
-:root[data-theme='dark'] {
- --bolt-elements-borderColor: theme('colors.alpha.white.10');
- --bolt-elements-borderColorActive: theme('colors.accent.500');
-
- --bolt-elements-bg-depth-1: theme('colors.gray.950');
- --bolt-elements-bg-depth-2: theme('colors.gray.900');
- --bolt-elements-bg-depth-3: theme('colors.gray.800');
- --bolt-elements-bg-depth-4: theme('colors.alpha.white.5');
-
- --bolt-elements-textPrimary: theme('colors.white');
- --bolt-elements-textSecondary: theme('colors.gray.400');
- --bolt-elements-textTertiary: theme('colors.gray.500');
-
- --bolt-elements-code-background: theme('colors.gray.800');
- --bolt-elements-code-text: theme('colors.white');
-
- --bolt-elements-button-primary-background: theme('colors.alpha.accent.10');
- --bolt-elements-button-primary-backgroundHover: theme('colors.alpha.accent.20');
- --bolt-elements-button-primary-text: theme('colors.accent.500');
-
- --bolt-elements-button-secondary-background: theme('colors.alpha.white.5');
- --bolt-elements-button-secondary-backgroundHover: theme('colors.alpha.white.10');
- --bolt-elements-button-secondary-text: theme('colors.white');
-
- --bolt-elements-button-danger-background: theme('colors.alpha.red.10');
- --bolt-elements-button-danger-backgroundHover: theme('colors.alpha.red.20');
- --bolt-elements-button-danger-text: theme('colors.red.500');
-
- --bolt-elements-item-contentDefault: theme('colors.alpha.white.50');
- --bolt-elements-item-contentActive: theme('colors.white');
- --bolt-elements-item-contentAccent: theme('colors.accent.500');
- --bolt-elements-item-contentDanger: theme('colors.red.500');
- --bolt-elements-item-backgroundDefault: rgba(255, 255, 255, 0);
- --bolt-elements-item-backgroundActive: theme('colors.alpha.white.10');
- --bolt-elements-item-backgroundAccent: theme('colors.alpha.accent.10');
- --bolt-elements-item-backgroundDanger: theme('colors.alpha.red.10');
-
- --bolt-elements-loader-background: theme('colors.alpha.gray.10');
- --bolt-elements-loader-progress: theme('colors.accent.500');
-
- --bolt-elements-artifacts-background: theme('colors.gray.900');
- --bolt-elements-artifacts-backgroundHover: theme('colors.alpha.white.5');
- --bolt-elements-artifacts-borderColor: var(--bolt-elements-borderColor);
- --bolt-elements-artifacts-inlineCode-background: theme('colors.gray.800');
- --bolt-elements-artifacts-inlineCode-text: theme('colors.white');
-
- --bolt-elements-actions-background: theme('colors.gray.900');
- --bolt-elements-actions-code-background: theme('colors.gray.800');
-
- --bolt-elements-messages-background: theme('colors.gray.800');
- --bolt-elements-messages-linkColor: theme('colors.accent.500');
- --bolt-elements-messages-code-background: theme('colors.gray.900');
- --bolt-elements-messages-inlineCode-background: theme('colors.gray.700');
- --bolt-elements-messages-inlineCode-text: var(--bolt-elements-textPrimary);
-
- --bolt-elements-icon-success: theme('colors.green.400');
- --bolt-elements-icon-error: theme('colors.red.400');
- --bolt-elements-icon-primary: theme('colors.gray.950');
- --bolt-elements-icon-secondary: theme('colors.gray.600');
- --bolt-elements-icon-tertiary: theme('colors.gray.500');
-
- --bolt-elements-dividerColor: theme('colors.gray.100');
-
- --bolt-elements-prompt-background: theme('colors.alpha.gray.80');
-
- --bolt-elements-sidebar-dropdownShadow: theme('colors.alpha.gray.30');
- --bolt-elements-sidebar-buttonBackgroundDefault: theme('colors.alpha.accent.10');
- --bolt-elements-sidebar-buttonBackgroundHover: theme('colors.alpha.accent.20');
- --bolt-elements-sidebar-buttonText: theme('colors.accent.500');
-
- --bolt-elements-preview-addressBar-background: var(--bolt-elements-bg-depth-1);
- --bolt-elements-preview-addressBar-backgroundHover: theme('colors.alpha.white.5');
- --bolt-elements-preview-addressBar-backgroundActive: var(--bolt-elements-bg-depth-1);
- --bolt-elements-preview-addressBar-text: var(--bolt-elements-textSecondary);
- --bolt-elements-preview-addressBar-textActive: var(--bolt-elements-textPrimary);
-
- --bolt-elements-terminals-background: var(--bolt-elements-bg-depth-1);
- --bolt-elements-terminals-buttonBackground: var(--bolt-elements-bg-depth-3);
-
- --bolt-elements-cta-background: theme('colors.alpha.white.10');
- --bolt-elements-cta-text: theme('colors.white');
-
- /* Terminal Colors */
- --bolt-terminal-background: var(--bolt-elements-terminals-background);
- --bolt-terminal-foreground: #eff0eb;
- --bolt-terminal-selection-background: #97979b33;
- --bolt-terminal-black: #000000;
- --bolt-terminal-red: #ff5c57;
- --bolt-terminal-green: #5af78e;
- --bolt-terminal-yellow: #f3f99d;
- --bolt-terminal-blue: #57c7ff;
- --bolt-terminal-magenta: #ff6ac1;
- --bolt-terminal-cyan: #9aedfe;
- --bolt-terminal-white: #f1f1f0;
- --bolt-terminal-brightBlack: #686868;
- --bolt-terminal-brightRed: #ff5c57;
- --bolt-terminal-brightGreen: #5af78e;
- --bolt-terminal-brightYellow: #f3f99d;
- --bolt-terminal-brightBlue: #57c7ff;
- --bolt-terminal-brightMagenta: #ff6ac1;
- --bolt-terminal-brightCyan: #9aedfe;
- --bolt-terminal-brightWhite: #f1f1f0;
-}
-
-/*
- * Element Tokens
- *
- * Hierarchy: Element Token -> (Element Token | Color Tokens) -> Primitives
- */
-:root {
- --header-height: 54px;
- --chat-max-width: 37rem;
- --chat-min-width: 640px;
- --workbench-width: min(calc(100% - var(--chat-min-width)), 1536px);
- --workbench-inner-width: var(--workbench-width);
- --workbench-left: calc(100% - var(--workbench-width));
-
- /* Toasts */
- --toastify-color-progress-success: var(--bolt-elements-icon-success);
- --toastify-color-progress-error: var(--bolt-elements-icon-error);
-
- /* Terminal */
- --bolt-elements-terminal-backgroundColor: var(--bolt-terminal-background);
- --bolt-elements-terminal-textColor: var(--bolt-terminal-foreground);
- --bolt-elements-terminal-cursorColor: var(--bolt-terminal-foreground);
- --bolt-elements-terminal-selection-backgroundColor: var(--bolt-terminal-selection-background);
- --bolt-elements-terminal-color-black: var(--bolt-terminal-black);
- --bolt-elements-terminal-color-red: var(--bolt-terminal-red);
- --bolt-elements-terminal-color-green: var(--bolt-terminal-green);
- --bolt-elements-terminal-color-yellow: var(--bolt-terminal-yellow);
- --bolt-elements-terminal-color-blue: var(--bolt-terminal-blue);
- --bolt-elements-terminal-color-magenta: var(--bolt-terminal-magenta);
- --bolt-elements-terminal-color-cyan: var(--bolt-terminal-cyan);
- --bolt-elements-terminal-color-white: var(--bolt-terminal-white);
- --bolt-elements-terminal-color-brightBlack: var(--bolt-terminal-brightBlack);
- --bolt-elements-terminal-color-brightRed: var(--bolt-terminal-brightRed);
- --bolt-elements-terminal-color-brightGreen: var(--bolt-terminal-brightGreen);
- --bolt-elements-terminal-color-brightYellow: var(--bolt-terminal-brightYellow);
- --bolt-elements-terminal-color-brightBlue: var(--bolt-terminal-brightBlue);
- --bolt-elements-terminal-color-brightMagenta: var(--bolt-terminal-brightMagenta);
- --bolt-elements-terminal-color-brightCyan: var(--bolt-terminal-brightCyan);
- --bolt-elements-terminal-color-brightWhite: var(--bolt-terminal-brightWhite);
-}
From 9d374d6e9cedfc42fd0de64b32c93db33a99e8ff Mon Sep 17 00:00:00 2001
From: Jad El Asmar <42979241+1elasmarjad@users.noreply.github.com>
Date: Sat, 13 Sep 2025 11:33:02 -0400
Subject: [PATCH 06/14] Added /challenge
---
app/components/chat/ChallengeChat.client.tsx | 233 +++++++++++++++++++
app/components/chat/ChallengeChat.tsx | 190 +++++++++++++++
app/routes/challenge.$id.tsx | 25 ++
3 files changed, 448 insertions(+)
create mode 100644 app/components/chat/ChallengeChat.client.tsx
create mode 100644 app/components/chat/ChallengeChat.tsx
create mode 100644 app/routes/challenge.$id.tsx
diff --git a/app/components/chat/ChallengeChat.client.tsx b/app/components/chat/ChallengeChat.client.tsx
new file mode 100644
index 0000000000..148982a5c6
--- /dev/null
+++ b/app/components/chat/ChallengeChat.client.tsx
@@ -0,0 +1,233 @@
+import { useStore } from '@nanostores/react';
+import type { Message } from 'ai';
+import { useChat } from 'ai/react';
+import { useAnimate } from 'framer-motion';
+import { memo, useEffect, useRef, useState } from 'react';
+import { cssTransition, toast, ToastContainer } from 'react-toastify';
+import { useMessageParser, usePromptEnhancer, useShortcuts, useSnapScroll } from '~/lib/hooks';
+import { useChatHistory } from '~/lib/persistence';
+import { chatStore } from '~/lib/stores/chat';
+import { workbenchStore } from '~/lib/stores/workbench';
+import { fileModificationsToHTML } from '~/utils/diff';
+import { cubicEasingFn } from '~/utils/easings';
+import { createScopedLogger, renderLogger } from '~/utils/logger';
+import { ChallengeChat as ChallengeChatBase } from './ChallengeChat';
+
+const toastAnimation = cssTransition({
+ enter: 'animated fadeInRight',
+ exit: 'animated fadeOutRight',
+});
+
+const logger = createScopedLogger('ChallengeChat');
+
+export function ChallengeChatClient() {
+ renderLogger.trace('ChallengeChat');
+
+ const { ready, initialMessages, storeMessageHistory } = useChatHistory();
+
+ return (
+ <>
+ {ready && }
+ {
+ return (
+
+ );
+ }}
+ icon={({ type }) => {
+ /**
+ * @todo Handle more types if we need them. This may require extra color palettes.
+ */
+ switch (type) {
+ case 'success': {
+ return ;
+ }
+ case 'error': {
+ return ;
+ }
+ }
+
+ return undefined;
+ }}
+ position="bottom-right"
+ pauseOnFocusLoss
+ transition={toastAnimation}
+ />
+ >
+ );
+}
+
+interface ChallengeChatProps {
+ initialMessages: Message[];
+ storeMessageHistory: (messages: Message[]) => Promise;
+}
+
+export const ChallengeChatImpl = memo(({ initialMessages, storeMessageHistory }: ChallengeChatProps) => {
+ useShortcuts();
+
+ const textareaRef = useRef(null);
+
+ const [chatStarted, setChatStarted] = useState(initialMessages.length > 0);
+
+ const { showChat } = useStore(chatStore);
+
+ const [animationScope, animate] = useAnimate();
+
+ const { messages, isLoading, input, handleInputChange, setInput, stop, append } = useChat({
+ api: '/api/chat',
+ onError: (error) => {
+ logger.error('Request failed\n\n', error);
+ toast.error('There was an error processing your request');
+ },
+ onFinish: () => {
+ logger.debug('Finished streaming');
+ },
+ initialMessages,
+ });
+
+ const { enhancingPrompt, promptEnhanced, enhancePrompt, resetEnhancer } = usePromptEnhancer();
+ const { parsedMessages, parseMessages } = useMessageParser();
+
+ const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200;
+
+ useEffect(() => {
+ chatStore.setKey('started', initialMessages.length > 0);
+ }, []);
+
+ useEffect(() => {
+ parseMessages(messages, isLoading);
+
+ if (messages.length > initialMessages.length) {
+ storeMessageHistory(messages).catch((error) => toast.error(error.message));
+ }
+ }, [messages, isLoading, parseMessages]);
+
+ const scrollTextArea = () => {
+ const textarea = textareaRef.current;
+
+ if (textarea) {
+ textarea.scrollTop = textarea.scrollHeight;
+ }
+ };
+
+ const abort = () => {
+ stop();
+ chatStore.setKey('aborted', true);
+ workbenchStore.abortAllActions();
+ };
+
+ useEffect(() => {
+ const textarea = textareaRef.current;
+
+ if (textarea) {
+ textarea.style.height = 'auto';
+
+ const scrollHeight = textarea.scrollHeight;
+
+ textarea.style.height = `${Math.min(scrollHeight, TEXTAREA_MAX_HEIGHT)}px`;
+ textarea.style.overflowY = scrollHeight > TEXTAREA_MAX_HEIGHT ? 'auto' : 'hidden';
+ }
+ }, [input, textareaRef]);
+
+ const runAnimation = async () => {
+ if (chatStarted) {
+ return;
+ }
+
+ await Promise.all([
+ animate('#challenge-intro', { opacity: 0, flex: 1 }, { duration: 0.2, ease: cubicEasingFn }),
+ ]);
+
+ chatStore.setKey('started', true);
+
+ setChatStarted(true);
+ };
+
+ const sendMessage = async (_event: React.UIEvent, messageInput?: string) => {
+ const _input = messageInput || input;
+
+ if (_input.length === 0 || isLoading) {
+ return;
+ }
+
+ /**
+ * @note (delm) Usually saving files shouldn't take long but it may take longer if there
+ * many unsaved files. In that case we need to block user input and show an indicator
+ * of some kind so the user is aware that something is happening. But I consider the
+ * happy case to be no unsaved files and I would expect users to save their changes
+ * before they send another message.
+ */
+ await workbenchStore.saveAllFiles();
+
+ const fileModifications = workbenchStore.getFileModifcations();
+
+ chatStore.setKey('aborted', false);
+
+ runAnimation();
+
+ if (fileModifications !== undefined) {
+ const diff = fileModificationsToHTML(fileModifications);
+
+ /**
+ * If we have file modifications we append a new user message manually since we have to prefix
+ * the user input with the file modifications and we don't want the new user input to appear
+ * in the prompt. Using `append` is almost the same as `handleSubmit` except that we have to
+ * manually reset the input and we'd have to manually pass in file attachments. However, those
+ * aren't relevant here.
+ */
+ append({ role: 'user', content: `${diff}\n\n${_input}` });
+
+ /**
+ * After sending a new message we reset all modifications since the model
+ * should now be aware of all the changes.
+ */
+ workbenchStore.resetAllFileModifications();
+ } else {
+ append({ role: 'user', content: _input });
+ }
+
+ setInput('');
+
+ resetEnhancer();
+
+ textareaRef.current?.blur();
+ };
+
+ const [messageRef, scrollRef] = useSnapScroll();
+
+ return (
+ {
+ if (message.role === 'user') {
+ return message;
+ }
+
+ return {
+ ...message,
+ content: parsedMessages[i] || '',
+ };
+ })}
+ enhancePrompt={() => {
+ enhancePrompt(input, (input) => {
+ setInput(input);
+ scrollTextArea();
+ });
+ }}
+ />
+ );
+});
\ No newline at end of file
diff --git a/app/components/chat/ChallengeChat.tsx b/app/components/chat/ChallengeChat.tsx
new file mode 100644
index 0000000000..eaa52da924
--- /dev/null
+++ b/app/components/chat/ChallengeChat.tsx
@@ -0,0 +1,190 @@
+import type { Message } from 'ai';
+import React, { type RefCallback } from 'react';
+import { ClientOnly } from 'remix-utils/client-only';
+import { Menu } from '~/components/sidebar/Menu.client';
+import { IconButton } from '~/components/ui/IconButton';
+import { Workbench } from '~/components/workbench/Workbench.client';
+import { classNames } from '~/utils/classNames';
+import { Messages } from './Messages.client';
+import { SendButton } from './SendButton.client';
+
+import styles from './BaseChat.module.scss';
+
+interface ChallengeChatProps {
+ textareaRef?: React.RefObject | undefined;
+ messageRef?: RefCallback | undefined;
+ scrollRef?: RefCallback | undefined;
+ showChat?: boolean;
+ chatStarted?: boolean;
+ isStreaming?: boolean;
+ messages?: Message[];
+ enhancingPrompt?: boolean;
+ promptEnhanced?: boolean;
+ input?: string;
+ handleStop?: () => void;
+ sendMessage?: (event: React.UIEvent, messageInput?: string) => void;
+ handleInputChange?: (event: React.ChangeEvent) => void;
+ enhancePrompt?: () => void;
+}
+
+const TEXTAREA_MIN_HEIGHT = 76;
+
+export const ChallengeChat = React.forwardRef(
+ (
+ {
+ textareaRef,
+ messageRef,
+ scrollRef,
+ showChat = true,
+ chatStarted = false,
+ isStreaming = false,
+ enhancingPrompt = false,
+ promptEnhanced = false,
+ messages,
+ input = '',
+ sendMessage,
+ handleInputChange,
+ enhancePrompt,
+ handleStop,
+ },
+ ref,
+ ) => {
+ const TEXTAREA_MAX_HEIGHT = chatStarted ? 400 : 200;
+
+ return (
+
+
{() => }
+
+
+ {!chatStarted && (
+
+
+ Build a counter
+
+
+
+ Create a React component with increment and decrement buttons that updates a counter value.
+
+
+ Your challenge timer will start right after the first prompt
+
+
+
+ )}
+
+
+ {() => {
+ return chatStarted ? (
+
+ ) : null;
+ }}
+
+
+
+
{/* Ghost Element */}
+
+
+
+
{() => }
+
+
+ );
+ },
+);
\ No newline at end of file
diff --git a/app/routes/challenge.$id.tsx b/app/routes/challenge.$id.tsx
new file mode 100644
index 0000000000..91ff4d4361
--- /dev/null
+++ b/app/routes/challenge.$id.tsx
@@ -0,0 +1,25 @@
+import { json, type MetaFunction } from '@remix-run/cloudflare';
+import { useParams } from '@remix-run/react';
+import { ClientOnly } from 'remix-utils/client-only';
+import { ChallengeChat as ChallengeChatFallback } from '~/components/chat/ChallengeChat';
+import { ChallengeChatClient } from '~/components/chat/ChallengeChat.client';
+import { Header } from '~/components/header/Header';
+
+export const meta: MetaFunction = () => {
+ return [{ title: 'Bolt - Challenge' }, { name: 'description', content: 'Talk with Bolt, an AI assistant from StackBlitz' }];
+};
+
+export const loader = () => json({});
+
+export default function Challenge() {
+ const { id } = useParams();
+
+ console.log('Challenge ID:', id);
+
+ return (
+
+
+ }>{() => }
+
+ );
+}
\ No newline at end of file
From bfa7b1e112dc172cc7764787f457456ff6bd2ba2 Mon Sep 17 00:00:00 2001
From: Jad El Asmar <42979241+1elasmarjad@users.noreply.github.com>
Date: Sat, 13 Sep 2025 11:45:06 -0400
Subject: [PATCH 07/14] colorway
---
app/components/header/Header.tsx | 5 +-
app/styles/variables.scss | 293 +++++++++++++++++++++++--------
2 files changed, 223 insertions(+), 75 deletions(-)
diff --git a/app/components/header/Header.tsx b/app/components/header/Header.tsx
index 355b309207..15cf4bfbd0 100644
--- a/app/components/header/Header.tsx
+++ b/app/components/header/Header.tsx
@@ -19,7 +19,10 @@ export function Header() {
)}
>
- {/* Removed bolt logo and sidebar icon */}
+
+
+
+
{() => }
diff --git a/app/styles/variables.scss b/app/styles/variables.scss
index 758406d4fa..133d4a69dd 100644
--- a/app/styles/variables.scss
+++ b/app/styles/variables.scss
@@ -1,106 +1,251 @@
-/* Color Tokens Dark Elegant Theme (Purple Accent) */
+/* Color Tokens Light Theme */
:root,
:root[data-theme='light'] {
- --bolt-elements-borderColor: #23263a; /* deep blue-gray border */
- --bolt-elements-borderColorActive: #7c3aed; /* purple accent */
+ --bolt-elements-borderColor: #E5E7EB;
+ --bolt-elements-borderColorActive: #8B5CF6;
- --bolt-elements-bg-depth-1: #181a20; /* dark background */
- --bolt-elements-bg-depth-2: #23263a;
- --bolt-elements-bg-depth-3: #23263a;
- --bolt-elements-bg-depth-4: #23263a;
+ --bolt-elements-bg-depth-1: #FAFAFA;
+ --bolt-elements-bg-depth-2: #F3F4F6;
+ --bolt-elements-bg-depth-3: #E5E7EB;
+ --bolt-elements-bg-depth-4: #F9FAFB;
- --bolt-elements-textPrimary: #f3f4f6; /* near white */
- --bolt-elements-textSecondary: #a1a1aa; /* muted gray */
- --bolt-elements-textTertiary: #7c3aed; /* purple accent */
+ --bolt-elements-textPrimary: #1F2937;
+ --bolt-elements-textSecondary: #6B7280;
+ --bolt-elements-textTertiary: #9CA3AF;
- --bolt-elements-code-background: #23263a;
- --bolt-elements-code-text: #e0e7ef;
+ --bolt-elements-code-background: #F3F4F6;
+ --bolt-elements-code-text: #1F2937;
- --bolt-elements-button-primary-background: #7c3aed;
- --bolt-elements-button-primary-backgroundHover: #a78bfa;
- --bolt-elements-button-primary-text: #fff;
+ --bolt-elements-button-primary-background: #EDE9FE;
+ --bolt-elements-button-primary-backgroundHover: #DDD6FE;
+ --bolt-elements-button-primary-text: #6B46C1;
- --bolt-elements-button-secondary-background: #23263a;
- --bolt-elements-button-secondary-backgroundHover: #2d3142;
- --bolt-elements-button-secondary-text: #f3f4f6;
+ --bolt-elements-button-secondary-background: #F9FAFB;
+ --bolt-elements-button-secondary-backgroundHover: #F3F4F6;
+ --bolt-elements-button-secondary-text: #374151;
- --bolt-elements-button-danger-background: #7c223a;
- --bolt-elements-button-danger-backgroundHover: #a8324a;
- --bolt-elements-button-danger-text: #ffb4d4;
+ --bolt-elements-button-danger-background: #FEF2F2;
+ --bolt-elements-button-danger-backgroundHover: #FECACA;
+ --bolt-elements-button-danger-text: #DC2626;
- --bolt-elements-item-contentDefault: #a1a1aa;
- --bolt-elements-item-contentActive: #f3f4f6;
- --bolt-elements-item-contentAccent: #7c3aed;
- --bolt-elements-item-contentDanger: #ffb4d4;
+ --bolt-elements-item-contentDefault: #9CA3AF;
+ --bolt-elements-item-contentActive: #1F2937;
+ --bolt-elements-item-contentAccent: #6B46C1;
+ --bolt-elements-item-contentDanger: #DC2626;
--bolt-elements-item-backgroundDefault: rgba(0, 0, 0, 0);
- --bolt-elements-item-backgroundActive: #23263a;
- --bolt-elements-item-backgroundAccent: #23263a;
- --bolt-elements-item-backgroundDanger: #7c223a;
+ --bolt-elements-item-backgroundActive: #F9FAFB;
+ --bolt-elements-item-backgroundAccent: #EDE9FE;
+ --bolt-elements-item-backgroundDanger: #FEF2F2;
- --bolt-elements-loader-background: #23263a;
- --bolt-elements-loader-progress: #7c3aed;
+ --bolt-elements-loader-background: #E5E7EB;
+ --bolt-elements-loader-progress: #8B5CF6;
- --bolt-elements-artifacts-background: #23263a;
- --bolt-elements-artifacts-backgroundHover: #2d3142;
+ --bolt-elements-artifacts-background: #FAFAFA;
+ --bolt-elements-artifacts-backgroundHover: #F9FAFB;
--bolt-elements-artifacts-borderColor: var(--bolt-elements-borderColor);
- --bolt-elements-artifacts-inlineCode-background: #23263a;
+ --bolt-elements-artifacts-inlineCode-background: #F3F4F6;
--bolt-elements-artifacts-inlineCode-text: var(--bolt-elements-textPrimary);
- --bolt-elements-actions-background: #181a20;
- --bolt-elements-actions-code-background: #23263a;
+ --bolt-elements-actions-background: #FAFAFA;
+ --bolt-elements-actions-code-background: #374151;
- --bolt-elements-messages-background: #23263a;
- --bolt-elements-messages-linkColor: #a78bfa;
- --bolt-elements-messages-code-background: #2d3142;
- --bolt-elements-messages-inlineCode-background: #2d3142;
- --bolt-elements-messages-inlineCode-text: #f3f4f6;
+ --bolt-elements-messages-background: #F3F4F6;
+ --bolt-elements-messages-linkColor: #8B5CF6;
+ --bolt-elements-messages-code-background: #374151;
+ --bolt-elements-messages-inlineCode-background: #E5E7EB;
+ --bolt-elements-messages-inlineCode-text: #374151;
- --bolt-elements-icon-success: #22c55e;
- --bolt-elements-icon-error: #ef4444;
- --bolt-elements-icon-primary: #f3f4f6;
- --bolt-elements-icon-secondary: #a1a1aa;
- --bolt-elements-icon-tertiary: #7c3aed;
+ --bolt-elements-icon-success: #10B981;
+ --bolt-elements-icon-error: #DC2626;
+ --bolt-elements-icon-primary: #1F2937;
+ --bolt-elements-icon-secondary: #6B7280;
+ --bolt-elements-icon-tertiary: #9CA3AF;
- --bolt-elements-dividerColor: #23263a;
+ --bolt-elements-dividerColor: #E5E7EB;
- --bolt-elements-prompt-background: #23263acc;
+ --bolt-elements-prompt-background: rgba(250, 250, 250, 0.8);
- --bolt-elements-sidebar-dropdownShadow: #23263a;
- --bolt-elements-sidebar-buttonBackgroundDefault: #23263a;
- --bolt-elements-sidebar-buttonBackgroundHover: #2d3142;
- --bolt-elements-sidebar-buttonText: #a78bfa;
+ --bolt-elements-sidebar-dropdownShadow: #E5E7EB;
+ --bolt-elements-sidebar-buttonBackgroundDefault: #EDE9FE;
+ --bolt-elements-sidebar-buttonBackgroundHover: #DDD6FE;
+ --bolt-elements-sidebar-buttonText: #6B46C1;
- --bolt-elements-preview-addressBar-background: #23263a;
- --bolt-elements-preview-addressBar-backgroundHover: #2d3142;
- --bolt-elements-preview-addressBar-backgroundActive: #2d3142;
+ --bolt-elements-preview-addressBar-background: #F3F4F6;
+ --bolt-elements-preview-addressBar-backgroundHover: #F9FAFB;
+ --bolt-elements-preview-addressBar-backgroundActive: #FAFAFA;
--bolt-elements-preview-addressBar-text: var(--bolt-elements-textSecondary);
--bolt-elements-preview-addressBar-textActive: var(--bolt-elements-textPrimary);
- --bolt-elements-terminals-background: #181a20;
+ --bolt-elements-terminals-background: #FAFAFA;
--bolt-elements-terminals-buttonBackground: var(--bolt-elements-bg-depth-4);
- --bolt-elements-cta-background: #23263a;
- --bolt-elements-cta-text: #f3f4f6;
+ --bolt-elements-cta-background: #F3F4F6;
+ --bolt-elements-cta-text: #1F2937;
/* Terminal Colors */
--bolt-terminal-background: var(--bolt-elements-terminals-background);
- --bolt-terminal-foreground: #f3f4f6;
- --bolt-terminal-selection-background: #7c3aed33;
+ --bolt-terminal-foreground: #333333;
+ --bolt-terminal-selection-background: #00000040;
--bolt-terminal-black: #000000;
- --bolt-terminal-red: #ef4444;
- --bolt-terminal-green: #22c55e;
- --bolt-terminal-yellow: #eab308;
- --bolt-terminal-blue: #7c3aed;
- --bolt-terminal-magenta: #a855f7;
- --bolt-terminal-cyan: #06b6d4;
- --bolt-terminal-white: #f3f4f6;
- --bolt-terminal-brightBlack: #a1a1aa;
- --bolt-terminal-brightRed: #ef4444;
- --bolt-terminal-brightGreen: #22c55e;
- --bolt-terminal-brightYellow: #eab308;
- --bolt-terminal-brightBlue: #a78bfa;
- --bolt-terminal-brightMagenta: #a78bfa;
- --bolt-terminal-brightCyan: #06b6d4;
- --bolt-terminal-brightWhite: #ffffff;
+ --bolt-terminal-red: #cd3131;
+ --bolt-terminal-green: #00bc00;
+ --bolt-terminal-yellow: #949800;
+ --bolt-terminal-blue: #0451a5;
+ --bolt-terminal-magenta: #bc05bc;
+ --bolt-terminal-cyan: #0598bc;
+ --bolt-terminal-white: #555555;
+ --bolt-terminal-brightBlack: #686868;
+ --bolt-terminal-brightRed: #cd3131;
+ --bolt-terminal-brightGreen: #00bc00;
+ --bolt-terminal-brightYellow: #949800;
+ --bolt-terminal-brightBlue: #0451a5;
+ --bolt-terminal-brightMagenta: #bc05bc;
+ --bolt-terminal-brightCyan: #0598bc;
+ --bolt-terminal-brightWhite: #a5a5a5;
}
+/* Color Tokens Dark Theme */
+:root,
+:root[data-theme='dark'] {
+ --bolt-elements-borderColor: rgba(139, 92, 246, 0.2);
+ --bolt-elements-borderColorActive: #A78BFA;
+
+ --bolt-elements-bg-depth-1: #0F0F23;
+ --bolt-elements-bg-depth-2: #1E1B2F;
+ --bolt-elements-bg-depth-3: #2D2A3E;
+ --bolt-elements-bg-depth-4: rgba(139, 92, 246, 0.05);
+
+ --bolt-elements-textPrimary: #F8FAFC;
+ --bolt-elements-textSecondary: #CBD5E1;
+ --bolt-elements-textTertiary: #94A3B8;
+
+ --bolt-elements-code-background: #2D2A3E;
+ --bolt-elements-code-text: #F8FAFC;
+
+ --bolt-elements-button-primary-background: rgba(139, 92, 246, 0.15);
+ --bolt-elements-button-primary-backgroundHover: rgba(139, 92, 246, 0.25);
+ --bolt-elements-button-primary-text: #A78BFA;
+
+ --bolt-elements-button-secondary-background: rgba(248, 250, 252, 0.05);
+ --bolt-elements-button-secondary-backgroundHover: rgba(248, 250, 252, 0.1);
+ --bolt-elements-button-secondary-text: #F8FAFC;
+
+ --bolt-elements-button-danger-background: rgba(239, 68, 68, 0.15);
+ --bolt-elements-button-danger-backgroundHover: rgba(239, 68, 68, 0.25);
+ --bolt-elements-button-danger-text: #FCA5A5;
+
+ --bolt-elements-item-contentDefault: rgba(248, 250, 252, 0.5);
+ --bolt-elements-item-contentActive: #F8FAFC;
+ --bolt-elements-item-contentAccent: #A78BFA;
+ --bolt-elements-item-contentDanger: #FCA5A5;
+ --bolt-elements-item-backgroundDefault: rgba(255, 255, 255, 0);
+ --bolt-elements-item-backgroundActive: rgba(248, 250, 252, 0.1);
+ --bolt-elements-item-backgroundAccent: rgba(139, 92, 246, 0.15);
+ --bolt-elements-item-backgroundDanger: rgba(239, 68, 68, 0.15);
+
+ --bolt-elements-loader-background: rgba(139, 92, 246, 0.2);
+ --bolt-elements-loader-progress: #A78BFA;
+
+ --bolt-elements-artifacts-background: #1E1B2F;
+ --bolt-elements-artifacts-backgroundHover: rgba(248, 250, 252, 0.05);
+ --bolt-elements-artifacts-borderColor: var(--bolt-elements-borderColor);
+ --bolt-elements-artifacts-inlineCode-background: #2D2A3E;
+ --bolt-elements-artifacts-inlineCode-text: #F8FAFC;
+
+ --bolt-elements-actions-background: #1E1B2F;
+ --bolt-elements-actions-code-background: #2D2A3E;
+
+ --bolt-elements-messages-background: #2D2A3E;
+ --bolt-elements-messages-linkColor: #A78BFA;
+ --bolt-elements-messages-code-background: #1E1B2F;
+ --bolt-elements-messages-inlineCode-background: #374151;
+ --bolt-elements-messages-inlineCode-text: var(--bolt-elements-textPrimary);
+
+ --bolt-elements-icon-success: #34D399;
+ --bolt-elements-icon-error: #FCA5A5;
+ --bolt-elements-icon-primary: #F8FAFC;
+ --bolt-elements-icon-secondary: #CBD5E1;
+ --bolt-elements-icon-tertiary: #94A3B8;
+
+ --bolt-elements-dividerColor: rgba(139, 92, 246, 0.15);
+
+ --bolt-elements-prompt-background: rgba(15, 15, 35, 0.8);
+
+ --bolt-elements-sidebar-dropdownShadow: rgba(0, 0, 0, 0.3);
+ --bolt-elements-sidebar-buttonBackgroundDefault: rgba(139, 92, 246, 0.15);
+ --bolt-elements-sidebar-buttonBackgroundHover: rgba(139, 92, 246, 0.25);
+ --bolt-elements-sidebar-buttonText: #A78BFA;
+
+ --bolt-elements-preview-addressBar-background: var(--bolt-elements-bg-depth-1);
+ --bolt-elements-preview-addressBar-backgroundHover: rgba(248, 250, 252, 0.05);
+ --bolt-elements-preview-addressBar-backgroundActive: var(--bolt-elements-bg-depth-1);
+ --bolt-elements-preview-addressBar-text: var(--bolt-elements-textSecondary);
+ --bolt-elements-preview-addressBar-textActive: var(--bolt-elements-textPrimary);
+
+ --bolt-elements-terminals-background: var(--bolt-elements-bg-depth-1);
+ --bolt-elements-terminals-buttonBackground: var(--bolt-elements-bg-depth-3);
+
+ --bolt-elements-cta-background: rgba(248, 250, 252, 0.1);
+ --bolt-elements-cta-text: #F8FAFC;
+
+ /* Terminal Colors */
+ --bolt-terminal-background: var(--bolt-elements-terminals-background);
+ --bolt-terminal-foreground: #F8FAFC;
+ --bolt-terminal-selection-background: rgba(139, 92, 246, 0.3);
+ --bolt-terminal-black: #0F0F23;
+ --bolt-terminal-red: #F87171;
+ --bolt-terminal-green: #34D399;
+ --bolt-terminal-yellow: #FBBF24;
+ --bolt-terminal-blue: #60A5FA;
+ --bolt-terminal-magenta: #A78BFA;
+ --bolt-terminal-cyan: #22D3EE;
+ --bolt-terminal-white: #F8FAFC;
+ --bolt-terminal-brightBlack: #6B7280;
+ --bolt-terminal-brightRed: #FCA5A5;
+ --bolt-terminal-brightGreen: #6EE7B7;
+ --bolt-terminal-brightYellow: #FCD34D;
+ --bolt-terminal-brightBlue: #93C5FD;
+ --bolt-terminal-brightMagenta: #C4B5FD;
+ --bolt-terminal-brightCyan: #67E8F9;
+ --bolt-terminal-brightWhite: #FFFFFF;
+}
+
+/*
+ * Element Tokens
+ *
+ * Hierarchy: Element Token -> (Element Token | Color Tokens) -> Primitives
+ */
+:root {
+ --header-height: 54px;
+ --chat-max-width: 37rem;
+ --chat-min-width: 640px;
+ --workbench-width: min(calc(100% - var(--chat-min-width)), 1536px);
+ --workbench-inner-width: var(--workbench-width);
+ --workbench-left: calc(100% - var(--workbench-width));
+
+ /* Toasts */
+ --toastify-color-progress-success: var(--bolt-elements-icon-success);
+ --toastify-color-progress-error: var(--bolt-elements-icon-error);
+
+ /* Terminal */
+ --bolt-elements-terminal-backgroundColor: var(--bolt-terminal-background);
+ --bolt-elements-terminal-textColor: var(--bolt-terminal-foreground);
+ --bolt-elements-terminal-cursorColor: var(--bolt-terminal-foreground);
+ --bolt-elements-terminal-selection-backgroundColor: var(--bolt-terminal-selection-background);
+ --bolt-elements-terminal-color-black: var(--bolt-terminal-black);
+ --bolt-elements-terminal-color-red: var(--bolt-terminal-red);
+ --bolt-elements-terminal-color-green: var(--bolt-terminal-green);
+ --bolt-elements-terminal-color-yellow: var(--bolt-terminal-yellow);
+ --bolt-elements-terminal-color-blue: var(--bolt-terminal-blue);
+ --bolt-elements-terminal-color-magenta: var(--bolt-terminal-magenta);
+ --bolt-elements-terminal-color-cyan: var(--bolt-terminal-cyan);
+ --bolt-elements-terminal-color-white: var(--bolt-terminal-white);
+ --bolt-elements-terminal-color-brightBlack: var(--bolt-terminal-brightBlack);
+ --bolt-elements-terminal-color-brightRed: var(--bolt-terminal-brightRed);
+ --bolt-elements-terminal-color-brightGreen: var(--bolt-terminal-brightGreen);
+ --bolt-elements-terminal-color-brightYellow: var(--bolt-terminal-brightYellow);
+ --bolt-elements-terminal-color-brightBlue: var(--bolt-terminal-brightBlue);
+ --bolt-elements-terminal-color-brightMagenta: var(--bolt-terminal-brightMagenta);
+ --bolt-elements-terminal-color-brightCyan: var(--bolt-terminal-brightCyan);
+ --bolt-elements-terminal-color-brightWhite: var(--bolt-terminal-brightWhite);
+}
From 080d3cdf1bc3773e70f6bf1ce14898363922b2c3 Mon Sep 17 00:00:00 2001
From: Jad El Asmar <42979241+1elasmarjad@users.noreply.github.com>
Date: Sat, 13 Sep 2025 11:53:25 -0400
Subject: [PATCH 08/14] Removed header
---
app/components/header/Header.tsx | 6 ------
1 file changed, 6 deletions(-)
diff --git a/app/components/header/Header.tsx b/app/components/header/Header.tsx
index 15cf4bfbd0..0199fc3555 100644
--- a/app/components/header/Header.tsx
+++ b/app/components/header/Header.tsx
@@ -18,12 +18,6 @@ export function Header() {
},
)}
>
-
{() => }
From 4963fbe53b36b7d1987094b9030500dd4e9e6f92 Mon Sep 17 00:00:00 2001
From: Jad El Asmar <42979241+1elasmarjad@users.noreply.github.com>
Date: Sat, 13 Sep 2025 12:02:43 -0400
Subject: [PATCH 09/14] Added challenge type
---
app/components/chat/ChallengeChat.client.tsx | 19 +++++++++--
app/components/chat/ChallengeChat.tsx | 7 ++--
app/lib/challenges.ts | 32 ++++++++++++++++++
app/routes/challenge.$id.tsx | 34 ++++++++++++++------
data/challenges.json | 12 +++++++
5 files changed, 90 insertions(+), 14 deletions(-)
create mode 100644 app/lib/challenges.ts
create mode 100644 data/challenges.json
diff --git a/app/components/chat/ChallengeChat.client.tsx b/app/components/chat/ChallengeChat.client.tsx
index 148982a5c6..54311ec0af 100644
--- a/app/components/chat/ChallengeChat.client.tsx
+++ b/app/components/chat/ChallengeChat.client.tsx
@@ -8,6 +8,7 @@ import { useMessageParser, usePromptEnhancer, useShortcuts, useSnapScroll } from
import { useChatHistory } from '~/lib/persistence';
import { chatStore } from '~/lib/stores/chat';
import { workbenchStore } from '~/lib/stores/workbench';
+import { type Challenge } from '~/lib/challenges';
import { fileModificationsToHTML } from '~/utils/diff';
import { cubicEasingFn } from '~/utils/easings';
import { createScopedLogger, renderLogger } from '~/utils/logger';
@@ -20,14 +21,24 @@ const toastAnimation = cssTransition({
const logger = createScopedLogger('ChallengeChat');
-export function ChallengeChatClient() {
+interface ChallengeChatClientProps {
+ challenge: Challenge;
+}
+
+export function ChallengeChatClient({ challenge }: ChallengeChatClientProps) {
renderLogger.trace('ChallengeChat');
const { ready, initialMessages, storeMessageHistory } = useChatHistory();
return (
<>
- {ready && }
+ {ready && (
+
+ )}
{
return (
@@ -60,11 +71,12 @@ export function ChallengeChatClient() {
}
interface ChallengeChatProps {
+ challenge: Challenge;
initialMessages: Message[];
storeMessageHistory: (messages: Message[]) => Promise;
}
-export const ChallengeChatImpl = memo(({ initialMessages, storeMessageHistory }: ChallengeChatProps) => {
+export const ChallengeChatImpl = memo(({ challenge, initialMessages, storeMessageHistory }: ChallengeChatProps) => {
useShortcuts();
const textareaRef = useRef(null);
@@ -200,6 +212,7 @@ export const ChallengeChatImpl = memo(({ initialMessages, storeMessageHistory }:
return (
| undefined;
messageRef?: RefCallback | undefined;
scrollRef?: RefCallback | undefined;
@@ -32,6 +34,7 @@ const TEXTAREA_MIN_HEIGHT = 76;
export const ChallengeChat = React.forwardRef(
(
{
+ challenge,
textareaRef,
messageRef,
scrollRef,
@@ -66,11 +69,11 @@ export const ChallengeChat = React.forwardRef
- Build a counter
+ {challenge.title}
- Create a React component with increment and decrement buttons that updates a counter value.
+ {challenge.question}
Your challenge timer will start right after the first prompt
diff --git a/app/lib/challenges.ts b/app/lib/challenges.ts
new file mode 100644
index 0000000000..efeb2ee21c
--- /dev/null
+++ b/app/lib/challenges.ts
@@ -0,0 +1,32 @@
+export interface Challenge {
+ id: string;
+ title: string;
+ question: string;
+}
+
+let challengesCache: Challenge[] | null = null;
+
+async function loadChallenges(): Promise {
+ if (challengesCache) {
+ return challengesCache;
+ }
+
+ try {
+ // In production, we'll need to handle file loading differently for Cloudflare Workers
+ const challengesModule = await import('../../data/challenges.json');
+ challengesCache = challengesModule.default as Challenge[];
+ return challengesCache;
+ } catch (error) {
+ console.error('Failed to load challenges:', error);
+ return [];
+ }
+}
+
+export async function getChallengeById(id: string): Promise {
+ const challenges = await loadChallenges();
+ return challenges.find(challenge => challenge.id === id) || null;
+}
+
+export async function getAllChallenges(): Promise {
+ return loadChallenges();
+}
\ No newline at end of file
diff --git a/app/routes/challenge.$id.tsx b/app/routes/challenge.$id.tsx
index 91ff4d4361..f54070a016 100644
--- a/app/routes/challenge.$id.tsx
+++ b/app/routes/challenge.$id.tsx
@@ -1,25 +1,41 @@
-import { json, type MetaFunction } from '@remix-run/cloudflare';
-import { useParams } from '@remix-run/react';
+import { json, type LoaderFunctionArgs, type MetaFunction } from '@remix-run/cloudflare';
+import { useLoaderData } from '@remix-run/react';
import { ClientOnly } from 'remix-utils/client-only';
import { ChallengeChat as ChallengeChatFallback } from '~/components/chat/ChallengeChat';
import { ChallengeChatClient } from '~/components/chat/ChallengeChat.client';
import { Header } from '~/components/header/Header';
+import { getChallengeById, type Challenge } from '~/lib/challenges';
-export const meta: MetaFunction = () => {
- return [{ title: 'Bolt - Challenge' }, { name: 'description', content: 'Talk with Bolt, an AI assistant from StackBlitz' }];
+export const meta: MetaFunction = ({ data }) => {
+ const title = data?.challenge ? `${data.challenge.title} - Challenge` : 'Challenge Not Found';
+ return [{ title }, { name: 'description', content: 'Code challenges powered by AI' }];
};
-export const loader = () => json({});
+export async function loader({ params }: LoaderFunctionArgs) {
+ const { id } = params;
-export default function Challenge() {
- const { id } = useParams();
+ if (!id) {
+ throw new Response('Challenge ID is required', { status: 400 });
+ }
+
+ const challenge = await getChallengeById(id);
+
+ if (!challenge) {
+ throw new Response('Challenge not found', { status: 404 });
+ }
- console.log('Challenge ID:', id);
+ return json({ challenge });
+}
+
+export default function Challenge() {
+ const { challenge } = useLoaderData();
return (
- }>{() => }
+ }>
+ {() => }
+
);
}
\ No newline at end of file
diff --git a/data/challenges.json b/data/challenges.json
new file mode 100644
index 0000000000..99d82fc03d
--- /dev/null
+++ b/data/challenges.json
@@ -0,0 +1,12 @@
+[
+ {
+ "id": "counter",
+ "title": "Build a Counter",
+ "question": "Make a click counter with +, -, and reset buttons. The value must never go below 0."
+ },
+ {
+ "id": "todo",
+ "title": "Todo List",
+ "question": "Build a todo list with add and delete functionality."
+ }
+]
\ No newline at end of file
From a2d13a18d31e7ae5dc4a3785f997ebf764346081 Mon Sep 17 00:00:00 2001
From: Jad El Asmar <42979241+1elasmarjad@users.noreply.github.com>
Date: Sat, 13 Sep 2025 14:29:09 -0400
Subject: [PATCH 10/14] Update ChallengeChat.tsx
---
app/components/chat/ChallengeChat.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/components/chat/ChallengeChat.tsx b/app/components/chat/ChallengeChat.tsx
index 220db09943..1972d17799 100644
--- a/app/components/chat/ChallengeChat.tsx
+++ b/app/components/chat/ChallengeChat.tsx
@@ -130,7 +130,7 @@ export const ChallengeChat = React.forwardRef
From 2c17ca1ab9595798f9704d82e881a2a0c9700601 Mon Sep 17 00:00:00 2001
From: azamjb
Date: Sat, 13 Sep 2025 15:07:09 -0400
Subject: [PATCH 11/14] navbar and profile page
---
app/components/header/Header.tsx | 25 +++-
app/routes/profile.tsx | 211 +++++++++++++++++++++++++++++++
2 files changed, 235 insertions(+), 1 deletion(-)
create mode 100644 app/routes/profile.tsx
diff --git a/app/components/header/Header.tsx b/app/components/header/Header.tsx
index 0199fc3555..9d0f70c2a2 100644
--- a/app/components/header/Header.tsx
+++ b/app/components/header/Header.tsx
@@ -4,20 +4,43 @@ import { chatStore } from '~/lib/stores/chat';
import { classNames } from '~/utils/classNames';
import { HeaderActionButtons } from './HeaderActionButtons.client';
import { ChatDescription } from '~/lib/persistence/ChatDescription.client';
+import { Link, useLocation } from '@remix-run/react';
export function Header() {
const chat = useStore(chatStore);
+ const location = useLocation();
return (