Skip to content

Commit 45768ec

Browse files
committed
fix typingfeedback performance
1 parent 06684a9 commit 45768ec

8 files changed

Lines changed: 551 additions & 21 deletions

File tree

apps/web/src/App.tsx

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { useCallback, useEffect, useRef, useState } from "react";
1+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
22
import { BackgroundGrid } from "./shared/components/BackgroundGrid";
33
import { EditorSurface } from "./features/editor/components/EditorSurface";
44
import { DocsDialog } from "./shared/components/DocsDialog";
55
import { FooterLink } from "./shared/components/FooterLink";
66
import { MenuBar } from "./shared/components/MenuBar";
77
import { EngineErrorNotice } from "./shared/components/EngineErrorNotice";
88
import { ExplorerSidebar } from "./features/explorer/components/ExplorerSidebar";
9+
import { CustomPromptDialog } from "./features/explorer/components/CustomPromptDialog";
910
import { DesktopShortcut } from "./shared/components/DesktopShortcut";
1011
import { localPacks } from "./content/localPacks";
1112
import type { PromptPackItem } from "./content/types";
@@ -27,13 +28,18 @@ import { useSessionRestart } from "./features/session/hooks/useSessionRestart";
2728
import { BottomStatsPanel } from "./features/stats/components/BottomStatsPanel";
2829
import { appStyles } from "./app/App.styles";
2930
import { useSyntaxThemeStore } from "./shared/syntax/themeStore";
31+
import { getCustomPrompts, deleteCustomPrompt } from "./features/explorer/storage/customPromptsRepository";
3032

3133
const promptEntries: PromptPackItem[] = localPacks.flatMap((pack) => pack.items);
3234

3335
const fallbackEntry: PromptPackItem | null = promptEntries[0] ?? null;
3436

3537
export default function App() {
3638
const [isDocsOpen, setDocsOpen] = useState(false);
39+
const [customPrompts, setCustomPrompts] = useState<PromptPackItem[]>([]);
40+
const [isCustomDialogOpen, setCustomDialogOpen] = useState(false);
41+
const [editingPrompt, setEditingPrompt] = useState<PromptPackItem | null>(null);
42+
3743
const targetSnippet = useSessionStore((state) => state.targetSnippet);
3844
const typed = useSessionStore((state) => state.typed);
3945
const attempts = useSessionStore((state) => state.attempts);
@@ -50,18 +56,49 @@ export default function App() {
5056

5157
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
5258

59+
// Load custom prompts on mount
60+
useEffect(() => {
61+
setCustomPrompts(getCustomPrompts());
62+
}, []);
63+
64+
// Merge built-in and custom prompts
65+
const allPrompts = useMemo(
66+
() => [...promptEntries, ...customPrompts],
67+
[customPrompts]
68+
);
69+
5370
const { selectedLanguage, setSelectedLanguage } = usePersistedLanguage(
54-
promptEntries,
71+
allPrompts,
5572
fallbackEntry?.language
5673
);
5774

5875
const { selectedPromptId, handlePromptSelect: applyPromptSelection } = usePromptSelection({
59-
prompts: promptEntries,
76+
prompts: allPrompts,
6077
language: selectedLanguage,
6178
fallbackEntry,
6279
onPromptChange: setPrompt,
6380
});
6481

82+
// Custom prompt handlers
83+
const handleAddCustomPrompt = useCallback(() => {
84+
setEditingPrompt(null);
85+
setCustomDialogOpen(true);
86+
}, []);
87+
88+
const handleEditCustomPrompt = useCallback((prompt: PromptPackItem) => {
89+
setEditingPrompt(prompt);
90+
setCustomDialogOpen(true);
91+
}, []);
92+
93+
const handleDeleteCustomPrompt = useCallback((id: string) => {
94+
deleteCustomPrompt(id);
95+
setCustomPrompts(getCustomPrompts());
96+
}, []);
97+
98+
const handleCustomPromptSave = useCallback(() => {
99+
setCustomPrompts(getCustomPrompts());
100+
}, []);
101+
65102
const { isReady: engineReady, error: engineError } = useEngineStatus();
66103

67104
useEffect(() => {
@@ -202,6 +239,7 @@ export default function App() {
202239
<div className={appStyles.explorerPane(isExplorerOpen)}>
203240
<ExplorerSidebar
204241
prompts={promptEntries}
242+
customPrompts={customPrompts}
205243
selectedLanguage={selectedLanguage}
206244
selectedPromptId={selectedPromptId}
207245
onOpenPrompt={(lang, id) => {
@@ -212,6 +250,9 @@ export default function App() {
212250
applyPromptSelection(id);
213251
}
214252
}}
253+
onAddCustomPrompt={handleAddCustomPrompt}
254+
onEditCustomPrompt={handleEditCustomPrompt}
255+
onDeleteCustomPrompt={handleDeleteCustomPrompt}
215256
/>
216257
</div>
217258
<BottomStatsPanel
@@ -236,6 +277,12 @@ export default function App() {
236277
<FooterLink href={GITHUB_URL} onDocsClick={handleDocsOpen} githubLabel="Repository" docsLabel="Documentation" />
237278
</div>
238279
<DocsDialog open={isDocsOpen} onClose={handleDocsClose} />
280+
<CustomPromptDialog
281+
isOpen={isCustomDialogOpen}
282+
onClose={() => setCustomDialogOpen(false)}
283+
onSave={handleCustomPromptSave}
284+
existingPrompt={editingPrompt ?? undefined}
285+
/>
239286
</>
240287
);
241288
}

apps/web/src/app/App.styles.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { uiTokens } from "../shared/styles/tokens";
22

33
export const appStyles = {
4-
root: "flex min-h-screen flex-col items-center justify-center overflow-hidden px-6 py-12 text-neutral-100",
5-
frameRegion: "relative flex w-full max-w-5xl flex-col items-center gap-6",
6-
main: "relative w-full max-w-5xl",
4+
root: "flex h-screen max-h-screen flex-col items-center justify-center overflow-hidden px-6 py-12 text-neutral-100",
5+
frameRegion: "relative flex w-full max-w-5xl flex-col items-center gap-6 max-h-full",
6+
main: "relative w-full max-w-5xl max-h-full",
77
glow: "pointer-events-none absolute inset-0 -z-10 rounded-[32px] bg-gradient-radial from-blue-500/5 via-transparent to-transparent blur-3xl",
88
window:
9-
`relative z-10 flex w-full min-h-[600px] flex-col overflow-hidden rounded-2xl ${uiTokens.glassSurface} shadow-[0_40px_120px_rgba(0,0,0,0.45)] will-change-transform`,
9+
`relative z-10 flex w-full max-h-full flex-col overflow-hidden rounded-2xl ${uiTokens.glassSurface} shadow-[0_40px_120px_rgba(0,0,0,0.45)] will-change-transform`,
1010
windowFullscreen:
1111
`fixed inset-0 z-30 m-0 flex h-screen w-screen min-h-0 flex-col overflow-hidden rounded-none shadow-none ${uiTokens.glassSurface} text-white`,
12-
windowBody: "relative flex flex-1 flex-col",
13-
editorColumn: "flex flex-1 min-h-0",
14-
editorInner: "flex-1 min-h-0",
12+
windowBody: "relative flex flex-1 flex-col min-h-0 overflow-hidden",
13+
editorColumn: "flex flex-1 min-h-0 overflow-auto",
14+
editorInner: "flex-1 min-h-0 overflow-auto",
1515
explorerPane: (open: boolean) =>
1616
`absolute top-0 right-0 bottom-0 transform transition-transform duration-300 ease-out will-change-transform backdrop-blur-2xl backdrop-saturate-150 ${open ? "translate-x-0 pointer-events-auto" : "translate-x-full pointer-events-none"
1717
}`,

apps/web/src/features/editor/components/TypingFeedback.tsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { useMemo } from "react";
22
import { motion } from "framer-motion";
3-
import { ANIMATION_DURATION } from "../../../config/constants";
43
import { feedbackClass, type CharacterFeedback } from "../../../shared/feedback/feedback";
54
import { getSyntaxColorClass, getTokenTypeAtIndex, type SyntaxToken } from "../../../shared/syntax/syntax";
65
import type { SyntaxThemeId } from "../../../shared/syntax/themes";
@@ -59,11 +58,9 @@ export function TypingFeedback({
5958

6059
if (isPending) {
6160
return (
62-
<motion.span
61+
<span
6362
key={item.index}
6463
className={typingFeedbackStyles.pendingSpan(syntaxClass)}
65-
layout
66-
transition={{ duration: ANIMATION_DURATION, ease: [0.25, 0.1, 0.25, 1] }}
6764
>
6865
{item.char}
6966
{isActive && (
@@ -73,7 +70,7 @@ export function TypingFeedback({
7370
transition={{ duration: 1, repeat: Number.POSITIVE_INFINITY, ease: "linear" }}
7471
/>
7572
)}
76-
</motion.span>
73+
</span>
7774
);
7875
}
7976

@@ -83,16 +80,14 @@ export function TypingFeedback({
8380
const targetDimClass = item.state === "error" && typedDisplay ? "opacity-40" : "";
8481

8582
return (
86-
<motion.span
83+
<span
8784
key={item.index}
8885
className={typingFeedbackStyles.typedSpan(syntaxClass, decorationClass)}
8986
title={
9087
item.typed && item.typed !== item.char
9188
? `Typed: ${normalizeWhitespace(item.typed)}`
9289
: undefined
9390
}
94-
layout
95-
transition={{ duration: ANIMATION_DURATION, ease: [0.25, 0.1, 0.25, 1] }}
9691
>
9792
<span className={targetDimClass}>{item.char}</span>
9893
{item.state === "error" && typedDisplay && (
@@ -107,7 +102,7 @@ export function TypingFeedback({
107102
transition={{ duration: 1, repeat: Number.POSITIVE_INFINITY, ease: "linear" }}
108103
/>
109104
)}
110-
</motion.span>
105+
</span>
111106
);
112107
})}
113108
</pre>

0 commit comments

Comments
 (0)