perf: P0 performance optimizations — memo, base64 cleanup, startup speed, bump v0.17.5

Six independent fixes targeting startup lag, streaming jank, and excessive payloads:

Step 0 — MessageItem React.memo + useMemo (MessageItem.tsx)
- Wrap MessageItem with React.memo so historical messages skip re-render
  when `message` ref is stable during streaming
- Add useMemo for parseToolBlocks + pairTools (deps: message.content),
  parseMessageFiles (deps: text), token_usage JSON.parse (deps: message.token_usage)
- Extract assistant content rendering into separate memo'd AssistantContent component
  to cache parseBatchPlan / parseImageGen chain (deps: displayText, messageId)
- Root cause: 50 msgs × 10 SSE events/s = 500 full parses/s causing UI jank

Step 1 — Strip base64 from new messages (ChatView.tsx, route.ts)
- ChatView: remove `data: f.data` from fileMeta in display content HTML comment
- route.ts: skip base64 data in fileAttachments when filePath exists (file already on disk)
- Root cause: base64 image data inflated message content to MB-level

Step 2 — Sanitize old messages in API (messages/route.ts)
- Add stripFileData() to strip base64 `data` fields from <!--files:...--> comments
  before returning messages from GET /api/chat/sessions/:id/messages
- Handles legacy data already stored with base64

Step 3 — Reduce initial fetch limit 100 → 30 (page.tsx, messages/route.ts)
- Default limit changed from 100 to 30 on both frontend and backend
- "Load more" functionality unchanged

Step 4 — ChatListPanel dedup requests (ChatListPanel.tsx)
- Remove duplicate pathname-dependent useEffect (was causing 2 fetches per navigation)
- Add AbortController to cancel in-flight requests on new fetch
- Add 300ms debounce for event-driven fetches (session-created/session-updated)

Step 5 — Electron startup: show window before server ready (electron/main.ts)
- createWindow() now accepts optional URL, defaults to inline loading HTML
- Loading page: dark theme, centered spinner, "Starting CodePilot..." text
- Startup: startServer() → createWindow(loading) → waitForServer() → loadURL(real)
- Also updated activate handler for same early-window pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
7418andClaude Opus 4.6 committed 2026-02-27 15:00:03 +08:00
1 parent c14fdb07ae
commit 132fdeb255
9 files changed
+232 -133

No files matched your search

+1 -1
View File
@@ -149,7 +149,7 @@ export async function POST(request: NextRequest) {
name: f.name,
type: f.type,
size: f.size,
data: f.data,
data: meta?.filePath ? '' : f.data, // Skip base64 data if file is already saved to disk
filePath: meta?.filePath,
};
})
@@ -2,6 +2,22 @@ import { NextRequest } from 'next/server';
import { getMessages, getSession } from '@/lib/db';
import type { MessagesResponse } from '@/types';
/** Strip base64 `data` fields from <!--files:...--> HTML comments in message content */
function stripFileData(content: string): string {
const match = content.match(/^<!--files:(.*?)-->/);
if (!match) return content;
try {
const files = JSON.parse(match[1]);
const cleaned = files.map((f: Record<string, unknown>) => {
const { data, ...rest } = f;
return rest;
});
return `<!--files:${JSON.stringify(cleaned)}-->${content.slice(match[0].length)}`;
} catch {
return content;
}
}
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
@@ -17,11 +33,16 @@ export async function GET(
const limitParam = searchParams.get('limit');
const beforeParam = searchParams.get('before');
const limit = limitParam ? Math.min(Math.max(parseInt(limitParam, 10) || 100, 1), 500) : 100;
const limit = limitParam ? Math.min(Math.max(parseInt(limitParam, 10) || 30, 1), 500) : 30;
const beforeRowId = beforeParam ? parseInt(beforeParam, 10) || undefined : undefined;
const { messages, hasMore } = getMessages(id, { limit, beforeRowId });
const response: MessagesResponse = { messages, hasMore };
// Sanitize: strip base64 data from file attachments in old messages
const sanitizedMessages = messages.map(m => ({
...m,
content: stripFileData(m.content),
}));
const response: MessagesResponse = { messages: sanitizedMessages, hasMore };
return Response.json(response);
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to fetch messages';