Skip to content
Merged
Show file tree
Hide file tree
Changes from 190 commits
Commits
Show all changes
192 commits
Select commit Hold shift + click to select a range
0751df4
update types
Kitenite Aug 26, 2025
161fc61
update dto to mappers
Kitenite Aug 26, 2025
f8216d2
update dto to mappers
Kitenite Aug 26, 2025
efc8855
clean up
Kitenite Aug 26, 2025
d51ebdb
add main branch
Kitenite Aug 26, 2025
f5b677b
add branch route
Kitenite Aug 26, 2025
f4bc424
update schema
Kitenite Aug 26, 2025
2bd9daf
clean up
Kitenite Aug 26, 2025
b4d6f19
saving state
Kitenite Aug 27, 2025
61a01e8
merge main
Kitenite Aug 27, 2025
769610c
update seed script
Kitenite Aug 27, 2025
d394578
working create
Kitenite Aug 27, 2025
4727ebe
fix types
Kitenite Aug 28, 2025
5eb19fc
fix types
Kitenite Aug 28, 2025
8386d2d
saving
Kitenite Aug 28, 2025
b535762
init
Kitenite Aug 28, 2025
7b353ff
starting project
Kitenite Aug 28, 2025
3164ef6
working start project
Kitenite Aug 28, 2025
9e7f4ee
working start project
Kitenite Aug 28, 2025
2762f96
init iframe
Kitenite Aug 28, 2025
1cee486
clean up
Kitenite Aug 28, 2025
02a7e13
show branch
Kitenite Aug 28, 2025
4d54c8d
simplify
Kitenite Aug 28, 2025
31fa8e2
clean up
Kitenite Aug 28, 2025
232a149
branch display
Kitenite Aug 28, 2025
b5994c9
clean up
Kitenite Aug 28, 2025
d081fe6
enforce single main branch
Kitenite Aug 28, 2025
6f9b760
add create idempotancy
Kitenite Aug 28, 2025
2afd496
Update apps/web/client/src/components/store/editor/branch/manager.ts
Kitenite Aug 28, 2025
1a9c002
better editor engine state
Kitenite Aug 28, 2025
6da4516
resistant editor engine
Kitenite Aug 28, 2025
8d6ef0b
save mobx learning
Kitenite Aug 28, 2025
f496fd3
update create manager
Kitenite Aug 28, 2025
7f288c2
add branch dropdown
Kitenite Aug 28, 2025
7016ac1
update timeAgo
Kitenite Aug 28, 2025
4c6b93a
clean up
Kitenite Aug 28, 2025
d052d11
clean up
Kitenite Aug 28, 2025
869ccbe
clean up
Kitenite Aug 29, 2025
174710c
handle no branch
Kitenite Aug 29, 2025
cde9c70
clean up branch ops
Kitenite Aug 29, 2025
2e55546
clean up branch ops
Kitenite Aug 29, 2025
7ed4fab
fix unit test
Kitenite Aug 29, 2025
d2de223
do not clear overlay
Kitenite Aug 29, 2025
f097e2e
clean up
Kitenite Aug 29, 2025
903edcd
download active sandbox code
Kitenite Aug 29, 2025
bea665c
clean up
Kitenite Aug 29, 2025
021803d
add branch dropdown to frame
Kitenite Aug 29, 2025
c870c34
frame branch
Kitenite Aug 29, 2025
4d9345b
refactor branches
Kitenite Aug 29, 2025
4b3341c
update branching
Kitenite Aug 29, 2025
6dc41f6
working fork
Kitenite Aug 29, 2025
e07e71a
working fork
Kitenite Aug 29, 2025
91e3b60
better real-time fork
Kitenite Aug 30, 2025
3b1b098
add branch tab
Kitenite Aug 30, 2025
60e0bfb
add branch management in tab
Kitenite Aug 30, 2025
c6ea50c
update styling
Kitenite Aug 30, 2025
4cbb1d9
add rename and delete branch
Kitenite Aug 30, 2025
30639fb
add rename and delete branch
Kitenite Aug 30, 2025
61bdb5e
add rename and delete branch
Kitenite Aug 30, 2025
4513408
update hooks
Kitenite Aug 30, 2025
272481b
state-managed branching
Kitenite Aug 30, 2025
944a626
clean up
Kitenite Aug 30, 2025
bb29a58
update translations
Kitenite Aug 30, 2025
6d6e8ff
update active branches
Kitenite Aug 30, 2025
9f40c90
update where clause
Kitenite Aug 30, 2025
508de48
clean up
Kitenite Aug 30, 2025
87997de
code tab
Kitenite Aug 30, 2025
9c3f9a8
code tab
Kitenite Aug 30, 2025
06423d7
revert claude
Kitenite Aug 30, 2025
7b57281
saving state
Kitenite Aug 30, 2025
ed734c7
update ide
Kitenite Aug 30, 2025
79ea8fe
update ide
Kitenite Aug 30, 2025
5d2fecf
working highlight
Kitenite Aug 30, 2025
51aec4b
working terminal with branches
Kitenite Aug 31, 2025
f1fa4dc
better fork loading state
Kitenite Aug 31, 2025
e48acf0
allow deleting active branch
Kitenite Aug 31, 2025
47ce11d
fix tests for github
Kitenite Aug 31, 2025
9840dcd
update build
Kitenite Aug 31, 2025
2475984
update publish
Kitenite Aug 31, 2025
17dccd7
add branch ID to dom element
Kitenite Aug 31, 2025
0510755
merge main
Kitenite Sep 2, 2025
9af020b
add migration script
Kitenite Sep 2, 2025
333e6b4
saving state
Kitenite Sep 2, 2025
ff5727c
update migration script
Kitenite Sep 2, 2025
f89a976
merge main
Kitenite Sep 2, 2025
f686f5a
handle deprecation
Kitenite Sep 2, 2025
497ca80
improve terminal
Kitenite Sep 2, 2025
f65f62d
update psl package
Kitenite Sep 2, 2025
7fcb024
update topbar ui
Kitenite Sep 2, 2025
a81b5bd
remove branch id from dom el
Kitenite Sep 2, 2025
3c34ed4
add branches to chat context
Kitenite Sep 2, 2025
fe37b4e
refactor template node manager
Kitenite Sep 2, 2025
78f66e9
consistent template node across branches
Kitenite Sep 2, 2025
827ed20
add branches to chat prompt
Kitenite Sep 3, 2025
fe0924f
chat with branches
Kitenite Sep 3, 2025
226c07a
add list branch tool
Kitenite Sep 3, 2025
33446d9
refactor read file
Kitenite Sep 3, 2025
6fb399e
update sandbox naming
Kitenite Sep 3, 2025
74a0d7d
better oids
Kitenite Sep 3, 2025
f337bfe
branch based history
Kitenite Sep 3, 2025
686e87c
clean up
Kitenite Sep 3, 2025
c80bcab
merge main
Kitenite Sep 3, 2025
fba64ff
Merge branch 'main' into feat/branching
Kitenite Sep 3, 2025
c950022
Merge branch 'main' into feat/branching
Kitenite Sep 3, 2025
75d904b
update unit test
Kitenite Sep 3, 2025
13a51c7
branch coloring
Kitenite Sep 3, 2025
136ba15
prevent deleting last frame in branch
Kitenite Sep 3, 2025
80ff36f
create blank sandbox
Kitenite Sep 3, 2025
0c51b3f
branch name
Kitenite Sep 3, 2025
4784876
sort input context pills
Kitenite Sep 4, 2025
c29e0a8
remove project context
Kitenite Sep 4, 2025
5125fb3
calculate frame position
Kitenite Sep 4, 2025
f453d44
use smart positioning
Kitenite Sep 4, 2025
8152a03
better positioning
Kitenite Sep 4, 2025
6717f58
add lru cache for files
Kitenite Sep 4, 2025
53aa1be
working bun test
Kitenite Sep 4, 2025
9687231
working test
Kitenite Sep 4, 2025
4ac6f57
save state
Kitenite Sep 4, 2025
f30abfe
caching
Kitenite Sep 4, 2025
61a60b6
better tree width
Kitenite Sep 4, 2025
e7088da
improve file tree
Kitenite Sep 4, 2025
310a38e
update file tree for cache
Kitenite Sep 4, 2025
be0f4e4
update project branch relation
Kitenite Sep 4, 2025
315aa3e
forking template with branches
Kitenite Sep 4, 2025
7e9c2f5
bun.lock update
Kitenite Sep 4, 2025
5e86dcc
add safe imperative handler callbacks
Kitenite Sep 4, 2025
afcb94a
better iframe reload pattern
Kitenite Sep 4, 2025
ded1ee2
move branch tab
Kitenite Sep 5, 2025
4eff157
fix code tab
Kitenite Sep 5, 2025
3e34066
clean up
Kitenite Sep 5, 2025
7e0c33c
file tree use discovered
Kitenite Sep 5, 2025
0dca2a6
clean up
Kitenite Sep 5, 2025
934a1b9
clean up
Kitenite Sep 5, 2025
d30219f
clean up
Kitenite Sep 5, 2025
374b85e
clean up
Kitenite Sep 5, 2025
bac4eb0
styling improvements
drfarrell Sep 5, 2025
9b6a602
clean up
Kitenite Sep 5, 2025
53407b4
clean up
Kitenite Sep 5, 2025
7f61893
clean up
Kitenite Sep 5, 2025
3bb146c
clean up
Kitenite Sep 5, 2025
6966a9a
fix branch controls
Kitenite Sep 5, 2025
dbf7140
rename window select
Kitenite Sep 5, 2025
38464dc
update top bar
Kitenite Sep 5, 2025
43a2ae0
error context
Kitenite Sep 5, 2025
67f4082
error context
Kitenite Sep 5, 2025
2b45cf0
update error handling
Kitenite Sep 5, 2025
94f2dfd
update tools
Kitenite Sep 5, 2025
c6d5fc1
update tools
Kitenite Sep 5, 2025
e4e4bf9
update tools
Kitenite Sep 5, 2025
dcd7c8b
save state
Kitenite Sep 5, 2025
891c262
add check error tool
Kitenite Sep 5, 2025
ef715b2
update style init
Kitenite Sep 6, 2025
b9734f5
update rename cache
Kitenite Sep 6, 2025
9672ff9
clear cache files
Kitenite Sep 6, 2025
fa20e7d
remove false positive
Kitenite Sep 6, 2025
571f14c
clean up
Kitenite Sep 6, 2025
fd445ef
correct import path
Kitenite Sep 6, 2025
910cde2
clean up
Kitenite Sep 6, 2025
ee177b8
clean up
Kitenite Sep 6, 2025
84d21fd
add zoom drift compensation
Kitenite Sep 6, 2025
02cfae5
add init
Kitenite Sep 6, 2025
53a8b66
clean up
Kitenite Sep 6, 2025
efcc3e5
clean up
Kitenite Sep 6, 2025
6073444
remove recursion
Kitenite Sep 7, 2025
b2b3a69
improve grep tool
Kitenite Sep 7, 2025
7430c8d
improve glob tool
Kitenite Sep 7, 2025
4fe0c96
clean up
Kitenite Sep 7, 2025
f1576ac
clean up
Kitenite Sep 7, 2025
2a12ab6
improve glob tool
Kitenite Sep 7, 2025
d2c47af
improve glob tool
Kitenite Sep 7, 2025
970347f
improve glob tool
Kitenite Sep 7, 2025
2d24f19
improve grep tool
Kitenite Sep 7, 2025
e5666d0
refactor tools
Kitenite Sep 7, 2025
70c143d
update unit tests
Kitenite Sep 7, 2025
ac9bc3f
update unit tests
Kitenite Sep 7, 2025
7eb5af7
make grep better
Kitenite Sep 7, 2025
cb43ed0
update readfile tool
Kitenite Sep 7, 2025
fa94c0f
improve read
Kitenite Sep 7, 2025
a81eccb
refactor tools
Kitenite Sep 7, 2025
a80ed34
refactor branch ids
Kitenite Sep 7, 2025
ee90da8
clean up
Kitenite Sep 7, 2025
480844f
refactor frame helper
Kitenite Sep 7, 2025
1079873
clean up
Kitenite Sep 7, 2025
3144720
clean up
Kitenite Sep 7, 2025
0daf7c2
clean up
Kitenite Sep 7, 2025
642f489
update branching logic
Kitenite Sep 7, 2025
0c95c25
fix unit test
Kitenite Sep 7, 2025
6d973f5
update migration script
Kitenite Sep 7, 2025
dffce62
update migration script
Kitenite Sep 7, 2025
75fb657
update migration script
Kitenite Sep 7, 2025
b4b337c
update migration script
Kitenite Sep 7, 2025
a15fbb5
optimize migration script
Kitenite Sep 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,51 @@
- Unit tests can be run with `bun test`
- Run type checking with `bun run typecheck`
- Apply database updates to local dev with `bun run db:push`
- Refrain from running the dev server

## MobX + React Best Practices

### Pattern for Provider Components with Observable Stores

When creating React providers that manage MobX observable stores (like
EditorEngine):

**✅ DO:**

- Use `useState(() => new Store())` for stable observable instances (MobX
recommended pattern)
- Keep refs (`engineRef.current`) to avoid stale closures in effects
- Use `setTimeout(() => store.clear(), 0)` for delayed cleanup to avoid race
conditions
- Separate project changes from branch updates with proper dependency arrays

**❌ DON'T:**

- Use `useMemo` for observable references - React may randomly "forget" them
(data loss risk)
- Clean up stores synchronously during navigation - causes "No branch selected"
errors
- Include the store instance in effect dependency arrays if it causes infinite
loops

### Example Pattern:

```tsx
const [store, setStore] = useState(() => new Store(props));
const storeRef = useRef<Store | null>(store);

useEffect(() => {
if (propChanged) {
setTimeout(() => storeRef.current?.clear(), 0); // Delayed cleanup
const newStore = new Store(newProps);
storeRef.current = newStore;
setStore(newStore);
}
}, [propChanged]);

useEffect(() => {
return () => setTimeout(() => storeRef.current?.clear(), 0);
}, []);
```

This maintains MobX reactivity while preventing cleanup race conditions.
15 changes: 9 additions & 6 deletions apps/backend/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
## Why a backend stack?

This is our server stack built in Supabase which you can also run locally or self-host.
This is our server stack built in Supabase which you can also run locally or
self-host.

Used to enable online capabilities such as managing users, collaborating, persisting data, etc.
Used to enable online capabilities such as managing users, collaborating,
persisting data, etc.

We will offer this as a hosted instance at some point. Ideally, the product should still work offline with no backend connection.
We will offer this as a hosted instance at some point. Ideally, the product
should still work offline with no backend connection.

## Usage

Expand All @@ -14,17 +17,17 @@ We will offer this as a hosted instance at some point. Ideally, the product shou
2. Install necessary packages

```bash
npm install
bun install
```

3. Run the supabase instance locally

```bash
npm run start
bun run start
```

4. Set up the latest snapshot of the database

```bash
npm run reset
bun run reset
```
3 changes: 0 additions & 3 deletions apps/web/client/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,3 @@ yarn-error.log*

# mastra
.mastra/

# Ignore preload script changes unless the script should be updated. Uncomment to update.
/public/onlook-preload-script.*
1 change: 1 addition & 0 deletions apps/web/client/messages/en.d.json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ declare const messages: {
"emptyState": "Select a window to edit its settings"
},
"brand": "Brand",
"branches": "Branches",
"apps": "Apps"
}
}
Expand Down
1 change: 1 addition & 0 deletions apps/web/client/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@
"emptyState": "Select a window to edit its settings"
},
"brand": "Brand",
"branches": "Branches",
"apps": "Apps"
}
}
Expand Down
1 change: 1 addition & 0 deletions apps/web/client/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@
"emptyState": "Seleccione una ventana para editar su configuración"
},
"brand": "Marca",
"branches": "Ramas",
"apps": "Aplicaciones"
}
}
Expand Down
1 change: 1 addition & 0 deletions apps/web/client/messages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@
"emptyState": "編集するウィンドウを選択してください"
},
"brand": "ブランド",
"branches": "ブランチ",
"apps": "アプリ"
}
}
Expand Down
1 change: 1 addition & 0 deletions apps/web/client/messages/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@
"emptyState": "설정을 편집할 창(윈도우)을 선택하세요"
},
"brand": "브랜드",
"branches": "브랜치",
"apps": "앱"
}
}
Expand Down
1 change: 1 addition & 0 deletions apps/web/client/messages/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@
"emptyState": "选择一个窗口以编辑其设置"
},
"brand": "品牌",
"branches": "分支",
"apps": "应用"
}
}
Expand Down
6 changes: 3 additions & 3 deletions apps/web/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,15 @@
"clsx": "^2.1.1",
"culori": "^4.0.1",
"date-fns": "^4.1.0",
"embla-carousel-react": "^8.6.0",
"flexsearch": "^0.8.160",
"freestyle-sandboxes": "^0.0.78",
"langfuse-vercel": "^3.38.4",
"localforage": "^1.10.0",
"lru-cache": "^11.2.1",
"lucide-react": "^0.486.0",
"mobx-react-lite": "^4.1.0",
"motion": "^12.6.3",
"next": ">=15.4.7",
"next": ">=15.5.2",
"next-intl": "^4.0.2",
"next-themes": "^0.4.6",
"octokit": "^5.0.3",
Expand Down Expand Up @@ -119,7 +119,7 @@
"@types/react-dom": "^19.0.0",
"@types/webfontloader": "^1.6.38",
"eslint": "^9.23.0",
"eslint-config-next": "^15.2.3",
"eslint-config-next": "^15.5.2",
"postcss": "^8.5.3",
"prettier": "^3.5.3",
"prettier-plugin-tailwindcss": "^0.6.11",
Expand Down
26 changes: 13 additions & 13 deletions apps/web/client/public/onlook-preload-script.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions apps/web/client/src/app/_components/hero/start-blank.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ export function StartBlank({
const newProject = await createProject({
project: {
name: 'New Project',
sandboxId,
sandboxUrl: previewUrl,
description: 'Your new blank project',
tags: ['blank'],
},
sandboxId,
sandboxUrl: previewUrl,
userId: user.id,
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useState, useRef, useEffect } from 'react';
import { Icons } from '@onlook/ui/icons';
import { Menu, Laptop } from 'lucide-react';
import { Laptop, Menu } from 'lucide-react';
import React, { useEffect, useRef, useState } from 'react';

export function ResponsiveWebsiteBlock() {
const [websiteWidth, setWebsiteWidth] = useState(400); // Initial width in pixels
Expand Down Expand Up @@ -52,7 +51,7 @@ export function ResponsiveWebsiteBlock() {
useEffect(() => {
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);

return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
Expand All @@ -63,7 +62,7 @@ export function ResponsiveWebsiteBlock() {
<div className="flex flex-col gap-4">
<div className="w-full h-100 bg-[#2E2C2D] rounded-lg mb-6 relative overflow-hidden" ref={containerRef}>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify nonstandard Tailwind utility classes exist in your config.

h-100, max-w-120, and cursor-ew-resize aren’t in Tailwind’s default scale. If not extended, use arbitrary values (e.g., h-[25rem], max-w-[30rem]) or cursor-col-resize.

Run:

If absent, replace:

  • h-100 -> h-[25rem] (or nearest default like h-96)
  • max-w-120 -> max-w-[30rem]
  • cursor-ew-resize -> cursor-col-resize

Also applies to: 100-100, 120-120, 126-126


🏁 Script executed:

#!/bin/bash
# Search for custom utilities in config
fd -H 'tailwind.config*' -t f | xargs -I{} sh -c 'echo "=== {} ==="; cat "{}"'
# Check usages
rg -n "h-100|max-w-120|cursor-ew-resize"

Length of output: 22824


🏁 Script executed:

#!/bin/bash
# Determine Tailwind CSS version to confirm support for dynamic spacing utilities
grep -R '"tailwindcss"' -n package.json pnpm-lock.yaml package-lock.json yarn.lock || echo "tailwindcss not found"

Length of output: 279


Replace undefined Tailwind classes with valid utilities
h-100, max-w-120, and cursor-ew-resize aren’t defined in your Tailwind config nor in the default scale. Update them to:

  • h-[25rem] (or nearest default like h-96) instead of h-100
  • max-w-[30rem] instead of max-w-120
  • cursor-col-resize instead of cursor-ew-resize

Apply the same replacements for any h-100, max-w-100/120/126, and cursor-ew-resize occurrences across the codebase.

🤖 Prompt for AI Agents
In
apps/web/client/src/app/_components/landing-page/feature-blocks/responsive-website.tsx
around line 63, replace the invalid Tailwind classes: change h-100 to a valid
size such as h-[25rem] (or h-96), replace any max-w-120 with max-w-[30rem], and
change cursor-ew-resize to cursor-col-resize; update the className on the div
and apply the same substitutions for any other occurrences of h-100,
max-w-100/120/126, and cursor-ew-resize across the codebase so all utilities use
valid Tailwind syntax.

{/* Mini Website Container */}
<div
<div
className="h-80 bg-[#E5E3DE] rounded-lg border border-[#D1CFC9] shadow-lg absolute left-1/2 top-12 transform -translate-x-1/2"
style={{ width: `${websiteWidth}px` }}
>
Expand Down Expand Up @@ -92,7 +91,7 @@ export function ResponsiveWebsiteBlock() {
<h1 className="text-xl font-serif mb-3">Le Fidgette</h1>
<p className="text-xs opacity-90 mb-6 text-balance">Creating natural shapes inspired by the natural world.</p>
</div>

{/* "View Work" Button */}
<div className="w-24 bg-[#8E837D] p-2 text-center cursor-pointer hover:bg-opacity-90 transition-opacity mb-12">
<p className="text-[10px] text-white font-medium tracking-wider">VIEW WORK</p>
Expand All @@ -117,21 +116,21 @@ export function ResponsiveWebsiteBlock() {
</div>
</div>
{/* Responsive Handles */}
<div
<div
className="absolute left-[-16px] top-1/2 transform -translate-y-1/2 p-4 py-20 -m-4 cursor-ew-resize group"
onMouseDown={(e) => handleMouseDown(e, 'left')}
>
<div className="w-1.5 h-20 bg-gray-400 group-hover:bg-gray-500 rounded-full transition-colors duration-200 shadow-lg"></div>
</div>
<div
<div
className="absolute right-[-16px] top-1/2 transform -translate-y-1/2 p-4 py-20 -m-4 cursor-ew-resize group"
onMouseDown={(e) => handleMouseDown(e, 'right')}
>
<div className="w-1.5 h-20 bg-gray-400 group-hover:bg-gray-500 rounded-full transition-colors duration-200 shadow-lg"></div>
</div>
Comment on lines +119 to 130
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make resize handles accessible (keyboard) and self-describing.

Add slider semantics with keyboard support (Left/Right arrows).

Apply this diff:

-                    <div
+                    <div
                         className="absolute left-[-16px] top-1/2 transform -translate-y-1/2 p-4 py-20 -m-4 cursor-ew-resize group"
-                        onMouseDown={(e) => handleMouseDown(e, 'left')}
+                        onMouseDown={(e) => handleMouseDown(e, 'left')}
+                        role="slider"
+                        tabIndex={0}
+                        aria-label="Resize preview (left handle)"
+                        aria-valuemin={200}
+                        aria-valuemax={600}
+                        aria-valuenow={websiteWidth}
+                        onKeyDown={(e) => handleKeyDown(e, 'left')}
                     >
-                    <div
+                    <div
                         className="absolute right-[-16px] top-1/2 transform -translate-y-1/2 p-4 py-20 -m-4 cursor-ew-resize group"
-                        onMouseDown={(e) => handleMouseDown(e, 'right')}
+                        onMouseDown={(e) => handleMouseDown(e, 'right')}
+                        role="slider"
+                        tabIndex={0}
+                        aria-label="Resize preview (right handle)"
+                        aria-valuemin={200}
+                        aria-valuemax={600}
+                        aria-valuenow={websiteWidth}
+                        onKeyDown={(e) => handleKeyDown(e, 'right')}
                     >

Add outside-range support code:

const handleKeyDown = (e: React.KeyboardEvent, handle: 'left' | 'right') => {
  const step = 10;
  if (e.key === 'ArrowLeft') setWebsiteWidth(w => clamp(w - step, 200, 600));
  if (e.key === 'ArrowRight') setWebsiteWidth(w => clamp(w + step, 200, 600));
};
🤖 Prompt for AI Agents
In
apps/web/client/src/app/_components/landing-page/feature-blocks/responsive-website.tsx
around lines 119 to 130, the resize handles are mouse-only and lack accessible
slider semantics; add keyboard support and ARIA so they behave like sliders.
Give each handle a focusable element (tabIndex=0), role="slider", an accessible
label (aria-label or aria-labelledby), and the ARIA attributes aria-valuemin,
aria-valuemax and aria-valuenow mapped to the current website width; add an
onKeyDown handler that implements ArrowLeft/ArrowRight to decrement/increment
width by a step (use clamp to constrain between 200 and 600) and wire that
handler to both handles while preserving the existing onMouseDown behavior.
Ensure step is consistent (e.g., 10) and update any state setters to use
functional updates so keyboard and mouse interactions remain in sync.

</div>
</div>

<div className="flex flex-row items-start gap-8 w-full">
{/* Icon + Title */}
<div className="flex flex-col items-start w-1/2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,44 @@ import { Terminal } from './terminal';

export const TerminalArea = observer(({ children }: { children: React.ReactNode }) => {
const editorEngine = useEditorEngine();
const terminalSessions = editorEngine.sandbox.session.terminalSessions;
const activeSessionId = editorEngine.sandbox.session.activeTerminalSessionId;
const branches = editorEngine.branches;

const [terminalHidden, setTerminalHidden] = useState(true);
// Collect terminal sessions from all branches
const allTerminalSessions = new Map<string, { name: string; branchName: string; branchId: string; sessionId: string; session: any }>();
let activeSessionId: string | null = null;

for (const branch of branches.allBranches) {
try {
const branchData = branches.getBranchById(branch.id);
if (!branchData) continue;

// Get the sandbox manager for this branch
const sandbox = branches.getSandboxById(branch.id);
if (!sandbox?.session?.terminalSessions) continue;

for (const [sessionId, session] of sandbox.session.terminalSessions) {
const key = `${branch.id}-${sessionId}`;
allTerminalSessions.set(key, {
name: session.name,
branchName: branch.name,
branchId: branch.id,
sessionId: sessionId,
session: session
});

if (!terminalSessions.size) {
return (
<div className="flex items-center justify-center h-full p-1 gap-2">
<Icons.LoadingSpinner className="animate-spin" />
<p className="text-foreground-secondary">Initializing Sandbox...</p>
</div>
)
// Set active session if this is the currently active branch and session
if (branch.id === branches.activeBranch.id && sessionId === sandbox.session.activeTerminalSessionId) {
activeSessionId = key;
}
Comment on lines +39 to +41
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard against undefined activeBranch to prevent runtime crash

branches.activeBranch.id will throw if activeBranch is unset; add a safe check.

-                if (branch.id === branches.activeBranch.id && sessionId === sandbox.session.activeTerminalSessionId) {
+                const activeBranchId = branches.activeBranch?.id;
+                if (activeBranchId && branch.id === activeBranchId && sessionId === sandbox.session.activeTerminalSessionId) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (branch.id === branches.activeBranch.id && sessionId === sandbox.session.activeTerminalSessionId) {
activeSessionId = key;
}
const activeBranchId = branches.activeBranch?.id;
if (activeBranchId && branch.id === activeBranchId && sessionId === sandbox.session.activeTerminalSessionId) {
activeSessionId = key;
}
🤖 Prompt for AI Agents
In apps/web/client/src/app/project/[id]/_components/bottom-bar/terminal-area.tsx
around lines 39 to 41, the current comparison accesses branches.activeBranch.id
directly which will throw if activeBranch is undefined; change the guard to
safely check for the presence of activeBranch before comparing (e.g., use
branches.activeBranch && branches.activeBranch.id or optional chaining like
branches.activeBranch?.id) so the comparison short-circuits when activeBranch is
unset and avoids a runtime crash.

}
} catch (error) {
// Skip branches that aren't properly initialized
continue;
}
}

const [terminalHidden, setTerminalHidden] = useState(true);

return (
<>
{terminalHidden ? (
Expand Down Expand Up @@ -77,21 +101,43 @@ export const TerminalArea = observer(({ children }: { children: React.ReactNode
terminalHidden ? 'h-0 w-0 invisible' : 'h-[22rem] w-[37rem]',
)}
>
<Tabs defaultValue={'cli'} value={activeSessionId} onValueChange={(value) => editorEngine.sandbox.session.activeTerminalSessionId = value}
className="w-full h-full">
<TabsList className="w-full h-8 rounded-none border-b border-border">
{Array.from(terminalSessions).map(([id, terminal]) => (
<TabsTrigger key={id} value={id} className="flex-1">{terminal.name}</TabsTrigger>
))}
</TabsList>
<div className="w-full h-full overflow-auto">
{Array.from(terminalSessions).map(([id]) => (
<TabsContent key={id} forceMount value={id} className="h-full" hidden={activeSessionId !== id}>
<Terminal hidden={terminalHidden} terminalSessionId={id} />
</TabsContent>
))}
{allTerminalSessions.size > 0 ? (
<Tabs defaultValue={'cli'} value={activeSessionId || ''} onValueChange={(value) => {
// Extract branch and session from the combined key
const terminalData = allTerminalSessions.get(value);
if (terminalData) {
// Switch to the branch first
editorEngine.branches.switchToBranch(terminalData.branchId);
// Then set the active terminal session for that branch
const sandbox = branches.getSandboxById(terminalData.branchId);
if (sandbox) {
sandbox.session.activeTerminalSessionId = terminalData.sessionId;
}
}
}}
className="w-full h-full">
<TabsList className="w-full h-8 rounded-none border-b border-border overflow-x-auto justify-start">
{Array.from(allTerminalSessions).map(([key, terminalData]) => (
<TabsTrigger key={key} value={key} className="flex-1">
<span className="truncate">
{terminalData.name} • {terminalData.branchName}
</span>
</TabsTrigger>
))}
</TabsList>
<div className="w-full h-full overflow-auto">
{Array.from(allTerminalSessions).map(([key, terminalData]) => (
<TabsContent key={key} forceMount value={key} className="h-full" hidden={activeSessionId !== key}>
<Terminal hidden={terminalHidden} terminalSessionId={terminalData.sessionId} branchId={terminalData.branchId} />
</TabsContent>
))}
</div>
</Tabs>
) : (
<div className="flex items-center justify-center h-full text-muted-foreground">
<span className="text-sm">No terminal sessions available</span>
</div>
</Tabs>
)}
</div >
</>
);
Expand Down
Loading