Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEditorEngine } from '@/components/store/editor';
import type { FrameData } from '@/components/store/editor/frames';
import { getRelativeMousePositionToFrameView } from '@/components/store/editor/overlay/utils';
import type { DomElement, ElementPosition, WebFrame } from '@onlook/models';
import type { DomElement, ElementPosition, Frame } from '@onlook/models';
import { EditorMode, MouseAction } from '@onlook/models';
import { toast } from '@onlook/ui/sonner';
import { cn } from '@onlook/ui/utils';
Expand All @@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite';
import { useCallback, useEffect, useMemo } from 'react';
import { RightClickMenu } from './right-click';

export const GestureScreen = observer(({ frame, isResizing }: { frame: WebFrame, isResizing: boolean }) => {
export const GestureScreen = observer(({ frame, isResizing }: { frame: Frame, isResizing: boolean }) => {
const editorEngine = useEditorEngine();

const getFrameData: () => FrameData | undefined = useCallback(() => {
Expand Down Expand Up @@ -94,19 +94,19 @@ export const GestureScreen = observer(({ frame, isResizing }: { frame: WebFrame,
() =>
throttle(async (e: React.MouseEvent<HTMLDivElement>) => {

if (editorEngine.move.shouldDrag) {
await editorEngine.move.drag(e, getRelativeMousePosition);
} else if (
editorEngine.state.editorMode === EditorMode.DESIGN ||
((editorEngine.state.editorMode === EditorMode.INSERT_DIV ||
editorEngine.state.editorMode === EditorMode.INSERT_TEXT ||
editorEngine.state.editorMode === EditorMode.INSERT_IMAGE) &&
!editorEngine.insert.isDrawing)
) {
await handleMouseEvent(e, MouseAction.MOVE);
} else if (editorEngine.insert.isDrawing) {
editorEngine.insert.draw(e);
}
if (editorEngine.move.shouldDrag) {
await editorEngine.move.drag(e, getRelativeMousePosition);
} else if (
editorEngine.state.editorMode === EditorMode.DESIGN ||
((editorEngine.state.editorMode === EditorMode.INSERT_DIV ||
editorEngine.state.editorMode === EditorMode.INSERT_TEXT ||
editorEngine.state.editorMode === EditorMode.INSERT_IMAGE) &&
!editorEngine.insert.isDrawing)
) {
await handleMouseEvent(e, MouseAction.MOVE);
} else if (editorEngine.insert.isDrawing) {
editorEngine.insert.draw(e);
}
}, 16),
[editorEngine, getRelativeMousePosition, handleMouseEvent],
);
Expand Down Expand Up @@ -148,9 +148,9 @@ export const GestureScreen = observer(({ frame, isResizing }: { frame: WebFrame,
if (!frameData) {
return;
}

editorEngine.move.cancelDragPreparation();

await editorEngine.move.end(e);
await editorEngine.insert.end(e, frameData.view);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { FrameType, type Frame, type WebFrame } from '@onlook/models';
import { type Frame } from '@onlook/models';
import { observer } from 'mobx-react-lite';
import { useRef, useState } from 'react';
import { useState } from 'react';
import { GestureScreen } from './gesture';
import { ResizeHandles } from './resize-handles';
import { RightClickMenu } from './right-click';
import { TopBar } from './top-bar';
import { WebFrameComponent, type WebFrameView } from './web-frame';
import { FrameComponent } from './view';

export const FrameView = observer(({ frame }: { frame: Frame }) => {
const webFrameRef = useRef<WebFrameView>(null);
const [isResizing, setIsResizing] = useState(false);

return (
Expand All @@ -17,14 +16,12 @@ export const FrameView = observer(({ frame }: { frame: Frame }) => {
style={{ transform: `translate(${frame.position.x}px, ${frame.position.y}px)` }}
>
<RightClickMenu>
<TopBar frame={frame as WebFrame} />
<TopBar frame={frame} />
</RightClickMenu>
<div className="relative">
<ResizeHandles frame={frame} setIsResizing={setIsResizing} />
{frame.type === FrameType.WEB && (
<WebFrameComponent frame={frame as WebFrame} ref={webFrameRef} />
)}
<GestureScreen frame={frame as WebFrame} isResizing={isResizing} />
<FrameComponent frame={frame} />
<GestureScreen frame={frame} isResizing={isResizing} />
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEditorEngine } from '@/components/store/editor';
import { LeftPanelTabValue, type PageNode, type WebFrame } from '@onlook/models';
import { LeftPanelTabValue, type Frame, type PageNode } from '@onlook/models';
import { Button } from '@onlook/ui/button';
import {
DropdownMenu,
Expand All @@ -16,7 +16,7 @@ import React, { useEffect, useMemo, useState } from 'react';
import { PageModal } from '../../left-panel/page-tab/page-modal';

interface PageSelectorProps {
frame: WebFrame;
frame: Frame;
className?: string;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEditorEngine } from '@/components/store/editor';
import type { WebFrame } from '@onlook/models';
import type { Frame } from '@onlook/models';
import { Button } from '@onlook/ui/button';
import { Icons } from '@onlook/ui/icons';
import { cn } from '@onlook/ui/utils';
Expand All @@ -10,7 +10,7 @@ import { HoverOnlyTooltip } from '../../editor-bar/hover-tooltip';
import { PageSelector } from './page-selector';

export const TopBar = observer(
({ frame }: { frame: WebFrame }) => {
({ frame }: { frame: Frame }) => {
const editorEngine = useEditorEngine();
const isSelected = editorEngine.frames.isSelected(frame.id);
const topBarRef = useRef<HTMLDivElement>(null);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { useEditorEngine } from '@/components/store/editor';
import type { WebFrame } from '@onlook/models';
import type { Frame } from '@onlook/models';
import {
PENPAL_PARENT_CHANNEL,
type PenpalChildMethods,
Expand All @@ -22,19 +22,19 @@ import {
type IframeHTMLAttributes,
} from 'react';

export type WebFrameView = HTMLIFrameElement & {
export type FrameView = HTMLIFrameElement & {
setZoomLevel: (level: number) => void;
supportsOpenDevTools: () => boolean;
reload: () => void;
isLoading: () => boolean;
} & PromisifiedPendpalChildMethods;

interface WebFrameViewProps extends IframeHTMLAttributes<HTMLIFrameElement> {
frame: WebFrame;
interface FrameViewProps extends IframeHTMLAttributes<HTMLIFrameElement> {
frame: Frame;
}

export const WebFrameComponent = observer(
forwardRef<WebFrameView, WebFrameViewProps>(({ frame, ...props }, ref) => {
export const FrameComponent = observer(
forwardRef<FrameView, FrameViewProps>(({ frame, ...props }, ref) => {
const editorEngine = useEditorEngine();
const iframeRef = useRef<HTMLIFrameElement>(null);
const zoomLevel = useRef(1);
Expand Down Expand Up @@ -229,11 +229,11 @@ export const WebFrameComponent = observer(
};
}, [penpalChild]);

useImperativeHandle(ref, (): WebFrameView => {
useImperativeHandle(ref, (): FrameView => {
const iframe = iframeRef.current;
if (!iframe) {
console.error(`${PENPAL_PARENT_CHANNEL} (${frame.id}) - Iframe - Not found`);
return {} as WebFrameView;
return {} as FrameView;
}

const syncMethods = {
Expand All @@ -252,11 +252,11 @@ export const WebFrameComponent = observer(
console.warn(
`${PENPAL_PARENT_CHANNEL} (${frame.id}) - Failed to setup penpal connection: iframeRemote is null`,
);
return Object.assign(iframe, syncMethods, remoteMethods) as WebFrameView;
return Object.assign(iframe, syncMethods, remoteMethods) as FrameView;
}

// Register the iframe with the editor engine
editorEngine.frames.registerView(frame, iframe as WebFrameView);
editorEngine.frames.registerView(frame, iframe as FrameView);

return Object.assign(iframe, {
...syncMethods,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const useStartProject = () => {
const apiUtils = api.useUtils();
const { data: user, isLoading: isUserLoading, error: userError } = api.user.get.useQuery();
const { data: project, isLoading: isProjectLoading, error: projectError } = api.project.get.useQuery({ projectId: editorEngine.projectId });
const { data: branch, isLoading: isBranchLoading, error: branchError } = api.branch.get.useQuery({ projectId: editorEngine.projectId });
const { data: canvasWithFrames, isLoading: isCanvasLoading, error: canvasError } = api.userCanvas.getWithFrames.useQuery({ projectId: editorEngine.projectId });
const { data: conversations, isLoading: isConversationsLoading, error: conversationsError } = api.chat.conversation.getAll.useQuery({ projectId: editorEngine.projectId });
const { data: creationRequest, isLoading: isCreationRequestLoading, error: creationRequestError } = api.project.createRequest.getPendingRequest.useQuery({ projectId: editorEngine.projectId });
Expand All @@ -40,7 +41,7 @@ export const useStartProject = () => {
useEffect(() => {
if (project) {
startSandbox(project);
editorEngine.screenshot.lastScreenshotAt = project.metadata.updatedPreviewImgAt;
editorEngine.screenshot.lastScreenshotAt = project.metadata.previewImg?.updatedAt ?? null;
}
}, [project]);

Expand Down Expand Up @@ -129,10 +130,11 @@ export const useStartProject = () => {
!isCanvasLoading &&
!isConversationsLoading &&
!isCreationRequestLoading &&
!isSandboxLoading;
!isSandboxLoading &&
!isBranchLoading;

setIsProjectReady(allQueriesResolved);
}, [isUserLoading, isProjectLoading, isCanvasLoading, isConversationsLoading, isCreationRequestLoading, isSandboxLoading]);
}, [isUserLoading, isProjectLoading, isCanvasLoading, isConversationsLoading, isCreationRequestLoading, isSandboxLoading, isBranchLoading]);

useEffect(() => {
setError(userError?.message ?? projectError?.message ?? canvasError?.message ?? conversationsError?.message ?? creationRequestError?.message ?? null);
Expand Down
4 changes: 2 additions & 2 deletions apps/web/client/src/app/projects/_components/select/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ export const SelectProject = ({ externalSearchQuery }: { externalSearchQuery?: s
const [spacing] = useState<number>(24);

// Templates
const projects = fetchedProjects?.filter(project => !project.tags?.includes(Tags.TEMPLATE)) ?? [];
const templateProjects = fetchedProjects?.filter(project => project.tags?.includes(Tags.TEMPLATE)) ?? [];
const projects = fetchedProjects?.filter(project => !project.metadata.tags.includes(Tags.TEMPLATE)) ?? [];
const templateProjects = fetchedProjects?.filter(project => project.metadata.tags.includes(Tags.TEMPLATE)) ?? [];
const shouldShowTemplate = templateProjects.length > 0;
const [selectedTemplate, setSelectedTemplate] = useState<Project | null>(null);
const [isTemplateModalOpen, setIsTemplateModalOpen] = useState(false);
Expand Down
2 changes: 1 addition & 1 deletion apps/web/client/src/app/projects/_components/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function Settings({ project, refetch }: { project: Project; refetch: () =
const [showRenameDialog, setShowRenameDialog] = useState(false);
const [projectName, setProjectName] = useState(project.name);
const isProjectNameEmpty = useMemo(() => projectName.length === 0, [projectName]);
const isTemplate = project.tags?.includes(Tags.TEMPLATE) || false;
const isTemplate = project.metadata.tags.includes(Tags.TEMPLATE) || false;

useEffect(() => {
setProjectName(project.name);
Expand Down
137 changes: 137 additions & 0 deletions apps/web/client/src/components/store/editor/branch/manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import type { Branch } from '@onlook/models';
import { makeAutoObservable } from 'mobx';
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 state reactive: import observable and runInAction.

Needed for observable maps and safe async mutations.

-import { toast } from '@onlook/ui/sonner';
-import { makeAutoObservable } from 'mobx';
+import { toast } from '@onlook/ui/sonner';
+import { makeAutoObservable, observable, runInAction } from 'mobx';
📝 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
import { makeAutoObservable } from 'mobx';
import { toast } from '@onlook/ui/sonner';
import { makeAutoObservable, observable, runInAction } from 'mobx';
🤖 Prompt for AI Agents
In apps/web/client/src/components/store/editor/branch/manager.ts around line 4,
the MobX state isn't prepared for observable maps or safe async mutations;
update the import to include observable and runInAction from 'mobx' and (where
maps are created) wrap map creation with observable(...) so they become
observable maps, and wrap any async mutation code paths in runInAction(...) to
perform state changes inside MobX actions.

import type { EditorEngine } from '../engine';
import { SandboxManager } from '../sandbox';

export class BranchManager {
private editorEngine: EditorEngine;
private currentBranchId: string | null = null;
private branchIdToSandboxManager = new Map<string, SandboxManager>();

constructor(editorEngine: EditorEngine) {
this.editorEngine = editorEngine;
makeAutoObservable(this);
}

get currentBranch(): string | null {
return this.currentBranchId;
}

getCurrentSandbox(): SandboxManager {
if (!this.currentBranchId) {
throw new Error('No branch selected. Call switchToBranch() first.');
}

if (!this.branchIdToSandboxManager.has(this.currentBranchId)) {
const sandboxManager = new SandboxManager(this.editorEngine);
this.branchIdToSandboxManager.set(this.currentBranchId, sandboxManager);
}

return this.branchIdToSandboxManager.get(this.currentBranchId)!;
}

async startCurrentBranchSandbox(): Promise<void> {
if (!this.currentBranchId) {
throw new Error('No branch selected. Call switchToBranch() first.');
}

const branch = await this.getBranchById(this.currentBranchId);
await this.getCurrentSandbox().session.start(branch.sandbox.id);
}

async switchToBranch(branchId: string): Promise<void> {
if (this.currentBranchId === branchId) {
return;
}

this.currentBranchId = branchId;
}

async createBranch(
name: string,
description?: string,
fromBranchId?: string,
isDefault = false
): Promise<Branch> {
const newBranch: Branch = {
id: `branch-${Date.now()}`,
name,
description: description || null,
createdAt: new Date(),
updatedAt: new Date(),
git: null,
sandbox: {
id: `sandbox-${Date.now()}`,
},
};

return newBranch;
}

async deleteBranch(branchId: string): Promise<void> {
if (branchId === this.currentBranchId) {
throw new Error('Cannot delete the currently active branch');
}

const sandboxManager = this.branchIdToSandboxManager.get(branchId);
if (sandboxManager) {
sandboxManager.clear();
this.branchIdToSandboxManager.delete(branchId);
}
}

async getDefaultBranch(): Promise<Branch> {
return {
id: 'main-branch-id',
name: 'main',
description: 'Default main branch',
createdAt: new Date(),
updatedAt: new Date(),
git: null,
sandbox: {
id: 'main-sandbox-id',
},
};
}

async getBranchById(branchId: string): Promise<Branch> {
return {
id: branchId,
name: branchId === 'main-branch-id' ? 'main' : `branch-${branchId}`,
description: null,
createdAt: new Date(),
updatedAt: new Date(),
git: null,
sandbox: {
id: `${branchId}-sandbox`,
},
};
}


private async createMainBranch(): Promise<Branch> {
return {
id: 'main-branch-id',
name: 'main',
description: 'Default main branch',
createdAt: new Date(),
updatedAt: new Date(),
git: null,
sandbox: {
id: 'main-sandbox-id',
},
};
}

async listBranches(): Promise<Branch[]> {
return [];
}

clear(): void {
for (const sandboxManager of this.branchIdToSandboxManager.values()) {
sandboxManager.clear();
}
this.branchIdToSandboxManager.clear();
this.currentBranchId = null;
}
}
Loading