Skip to content

Commit f0aa58c

Browse files
authored
Merge pull request #1708 from xKevIsDev/main
fix: various performance issues
2 parents 50a5196 + cfc2fc7 commit f0aa58c

File tree

4 files changed

+84
-101
lines changed

4 files changed

+84
-101
lines changed

app/components/workbench/DiffView.tsx

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -542,8 +542,53 @@ const FileInfo = memo(
542542
},
543543
);
544544

545+
// Create and manage a single highlighter instance at the module level
546+
let highlighterInstance: any = null;
547+
let highlighterPromise: Promise<any> | null = null;
548+
549+
const getSharedHighlighter = async () => {
550+
if (highlighterInstance) {
551+
return highlighterInstance;
552+
}
553+
554+
if (highlighterPromise) {
555+
return highlighterPromise;
556+
}
557+
558+
highlighterPromise = getHighlighter({
559+
themes: ['github-dark', 'github-light'],
560+
langs: [
561+
'typescript',
562+
'javascript',
563+
'json',
564+
'html',
565+
'css',
566+
'jsx',
567+
'tsx',
568+
'python',
569+
'php',
570+
'java',
571+
'c',
572+
'cpp',
573+
'csharp',
574+
'go',
575+
'ruby',
576+
'rust',
577+
'plaintext',
578+
],
579+
});
580+
581+
highlighterInstance = await highlighterPromise;
582+
highlighterPromise = null;
583+
584+
// Clear the promise once resolved
585+
return highlighterInstance;
586+
};
587+
545588
const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language }: CodeComparisonProps) => {
546589
const [isFullscreen, setIsFullscreen] = useState(false);
590+
591+
// Use state to hold the shared highlighter instance
547592
const [highlighter, setHighlighter] = useState<any>(null);
548593
const theme = useStore(themeStore);
549594

@@ -554,34 +599,32 @@ const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language }
554599
const { unifiedBlocks, hasChanges, isBinary, error } = useProcessChanges(beforeCode, afterCode);
555600

556601
useEffect(() => {
557-
getHighlighter({
558-
themes: ['github-dark', 'github-light'],
559-
langs: [
560-
'typescript',
561-
'javascript',
562-
'json',
563-
'html',
564-
'css',
565-
'jsx',
566-
'tsx',
567-
'python',
568-
'php',
569-
'java',
570-
'c',
571-
'cpp',
572-
'csharp',
573-
'go',
574-
'ruby',
575-
'rust',
576-
'plaintext',
577-
],
578-
}).then(setHighlighter);
579-
}, []);
602+
// Fetch the shared highlighter instance
603+
getSharedHighlighter().then(setHighlighter);
604+
605+
/*
606+
* No cleanup needed here for the highlighter instance itself,
607+
* as it's managed globally. Shiki instances don't typically
608+
* need disposal unless you are dynamically loading/unloading themes/languages.
609+
* If you were dynamically loading, you might need a more complex
610+
* shared instance manager with reference counting or similar.
611+
* For static themes/langs, a single instance is sufficient.
612+
*/
613+
}, []); // Empty dependency array ensures this runs only once on mount
580614

581615
if (isBinary || error) {
582616
return renderContentWarning(isBinary ? 'binary' : 'error');
583617
}
584618

619+
// Render a loading state or null while highlighter is not ready
620+
if (!highlighter) {
621+
return (
622+
<div className="h-full flex items-center justify-center">
623+
<div className="text-bolt-elements-textTertiary">Loading diff...</div>
624+
</div>
625+
);
626+
}
627+
585628
return (
586629
<FullscreenOverlay isFullscreen={isFullscreen}>
587630
<div className="w-full h-full flex flex-col">
@@ -602,7 +645,7 @@ const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language }
602645
lineNumber={block.lineNumber}
603646
content={block.content}
604647
type={block.type}
605-
highlighter={highlighter}
648+
highlighter={highlighter} // Pass the shared instance
606649
language={language}
607650
block={block}
608651
theme={theme}

app/lib/common/prompts/new-prompt.ts

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,6 @@ The year is 2025.
4545
</technology_preferences>
4646
4747
<running_shell_commands_info>
48-
With each user request, you are provided with information about the shell command that is currently running.
49-
50-
Example:
51-
52-
<bolt_running_commands>
53-
npm run dev
54-
</bolt_running_commands>
55-
5648
CRITICAL:
5749
- NEVER mention or reference the XML tags or structure of this process list in your responses
5850
- DO NOT repeat or directly quote any part of the command information provided
@@ -286,6 +278,7 @@ The year is 2025.
286278
287279
- Follow the guidelines for shell commands.
288280
- Use the start action type over the shell type ONLY when the command is intended to start the project.
281+
- IMPORTANT: Always execute the start command after executing a shell command.
289282
290283
- file: For creating new files or updating existing files. Add \`filePath\` and \`contentType\` attributes:
291284
@@ -317,15 +310,15 @@ The year is 2025.
317310
11. Prioritize installing required dependencies by updating \`package.json\` first.
318311
319312
- If a \`package.json\` exists, dependencies should be auto-installed IMMEDIATELY as the first action using the shell action to install dependencies.
320-
- If you need to update the \`package.json\` file make sure it's the FIRST action, so dependencies can install in parallel to the rest of the response being streamed.
313+
- If you need to update the \`package.json\` file make sure it's the FIRST action, so dependencies can install in parallel to the rest of the response being streamed this should ALWAYS be done inside the artifact.
321314
- \`npm install\` will not automatically run every time \`package.json\` is updated, so you need to include a shell action to install dependencies.
322315
- Only proceed with other actions after the required dependencies have been added to the \`package.json\`.
323316
324317
IMPORTANT: Add all required dependencies to the \`package.json\` file upfront. Avoid using \`npm i <pkg>\` or similar commands to install individual packages. Instead, update the \`package.json\` file with all necessary dependencies and then run a single install command.
325318
326319
12. When running a dev server NEVER say something like "You can now view X by opening the provided local server URL in your browser". The preview will be opened automatically or by the user manually!
327320
328-
13. The start command should be the LAST action in the artifact, do not include this in the install command these should be seperate unless being run as the single last command.
321+
13. Always include a start command in the artifact, The start command should be the LAST action in the artifact.
329322
</artifact_instructions>
330323
331324
<design_instructions>
@@ -647,7 +640,8 @@ function App() {
647640
}
648641
649642
export default App;</boltAction>
650-
<boltAction type="file" filePath="src/App.css" contentType="content">.container {
643+
644+
<boltAction type="file" filePath="src/App.css" contentType="content"> {
651645
max-width: 800px;
652646
margin: 0 auto;
653647
padding: 20px;
@@ -698,25 +692,6 @@ export default App;</boltAction>
698692
npm run dev
699693
</boltAction>
700694
</boltArtifact>
701-
702-
I've created a demonstration of three different ways to center a div:
703-
704-
1. **Using Flexbox** - This is the most recommended modern approach:
705-
- Set the parent container to \`display: flex\`
706-
- Use \`justify-content: center\` for horizontal centering
707-
- Use \`align-items: center\` for vertical centering
708-
709-
2. **Using CSS Grid** - Even simpler than flexbox in some cases:
710-
- Set the parent container to \`display: grid\`
711-
- Use \`place-items: center\` to center in both directions at once
712-
713-
3. **Using Position Absolute** - The traditional method:
714-
- Set the parent to \`position: relative\`
715-
- Set the child to \`position: absolute\`
716-
- Use \`top: 50%; left: 50%\` to position at the center
717-
- Use \`transform: translate(-50%, -50%)\` to adjust for the element's size
718-
719-
The flexbox method is generally the most versatile and recommended approach for most centering needs in modern web development.</assistant_response>
720695
</example>
721696
</examples>`;
722697

app/lib/persistence/useChatHistory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ ${value.content}
199199

200200
const takeSnapshot = useCallback(
201201
async (chatIdx: string, files: FileMap, _chatId?: string | undefined, chatSummary?: string) => {
202-
const id = _chatId || chatId.get();
202+
const id = chatId.get();
203203

204204
if (!id || !db) {
205205
return;

app/lib/stores/files.ts

Lines changed: 11 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -692,27 +692,9 @@ export class FilesStore {
692692
#processEventBuffer(events: Array<[events: PathWatcherEvent[]]>) {
693693
const watchEvents = events.flat(2);
694694

695-
for (const { type, path: eventPath, buffer } of watchEvents) {
695+
for (const { type, path, buffer } of watchEvents) {
696696
// remove any trailing slashes
697-
const sanitizedPath = eventPath.replace(/\/+$/g, '');
698-
699-
// Skip processing if this file/folder was explicitly deleted
700-
if (this.#deletedPaths.has(sanitizedPath)) {
701-
continue;
702-
}
703-
704-
let isInDeletedFolder = false;
705-
706-
for (const deletedPath of this.#deletedPaths) {
707-
if (sanitizedPath.startsWith(deletedPath + '/')) {
708-
isInDeletedFolder = true;
709-
break;
710-
}
711-
}
712-
713-
if (isInDeletedFolder) {
714-
continue;
715-
}
697+
const sanitizedPath = path.replace(/\/+$/g, '');
716698

717699
switch (type) {
718700
case 'add_dir': {
@@ -738,38 +720,21 @@ export class FilesStore {
738720
}
739721

740722
let content = '';
723+
724+
/**
725+
* @note This check is purely for the editor. The way we detect this is not
726+
* bullet-proof and it's a best guess so there might be false-positives.
727+
* The reason we do this is because we don't want to display binary files
728+
* in the editor nor allow to edit them.
729+
*/
741730
const isBinary = isBinaryFile(buffer);
742731

743-
if (isBinary && buffer) {
744-
// For binary files, we need to preserve the content as base64
745-
content = Buffer.from(buffer).toString('base64');
746-
} else if (!isBinary) {
732+
if (!isBinary) {
747733
content = this.#decodeFileContent(buffer);
748-
749-
/*
750-
* If the content is a single space and this is from our empty file workaround,
751-
* convert it back to an actual empty string
752-
*/
753-
if (content === ' ' && type === 'add_file') {
754-
content = '';
755-
}
756-
}
757-
758-
const existingFile = this.files.get()[sanitizedPath];
759-
760-
if (existingFile?.type === 'file' && existingFile.isBinary && existingFile.content && !content) {
761-
content = existingFile.content;
762734
}
763735

764-
// Preserve lock state if the file already exists
765-
const isLocked = existingFile?.type === 'file' ? existingFile.isLocked : false;
736+
this.files.setKey(sanitizedPath, { type: 'file', content, isBinary });
766737

767-
this.files.setKey(sanitizedPath, {
768-
type: 'file',
769-
content,
770-
isBinary,
771-
isLocked,
772-
});
773738
break;
774739
}
775740
case 'remove_file': {

0 commit comments

Comments
 (0)