History

Fix 5 bugs: dropped events, lastVisitedAt, bootstrap thread, duplicate config, delete race

- Remove early return guard in EventRouter that silently dropped all provider events when no thread was active (e.g., user on index route). Use a ref for activeThreadId to avoid re-subscribing the event listener on navigation. - Add VISIT_THREAD action to update lastVisitedAt when navigating to a thread, restoring the ability to dismiss the 'Completed' pill in the sidebar. - Restore ADD_THREAD dispatch in AutoProjectBootstrap so first-time browser users get an initial thread instead of a blank placeholder. - Delete duplicate vite.config.js that duplicated the existing vite.config.ts. - Await navigation before dispatching DELETE_THREAD in sidebar to prevent the route guard redirect from racing and overriding the fallback thread navigation.

Cursor Agent committed 8 months ago

Commit 141f6d21 parent de9c0595 files · +27 −50Browse files

Unified · Split

5 files
  1. apps/web/src/components/Sidebar.tsx +3 −3
  2. apps/web/src/routes/__root.tsx +9 −3
  3. apps/web/src/routes/_chat.$threadId.tsx +4 −2
  4. apps/web/src/store.ts +11 −1
  5. apps/web/vite.config.js +0 −41
apps/web/src/components/Sidebar.tsx+3−3
@@ -310,18 +310,18 @@ export default function Sidebar() { · 309 unmodified lines
310310
311311 const shouldNavigateToFallback = routeThreadId === threadId;
312312 const fallbackThreadId = state.threads.find((entry) => entry.id !== threadId)?.id ?? null;
313− dispatch({ type: "DELETE_THREAD", threadId });
314313 if (shouldNavigateToFallback) {
315314 if (fallbackThreadId) {
316− void navigate({
315+ await navigate({
317316 to: "/$threadId",
318317 params: { threadId: fallbackThreadId },
319318 replace: true,
320319 });
321320 } else {
322− void navigate({ to: "/", replace: true });
321+ await navigate({ to: "/", replace: true });
323322 }
324323 }
324+ dispatch({ type: "DELETE_THREAD", threadId });
325325
326326 if (!shouldDeleteWorktree || !orphanedWorktreePath || !threadProject) {
327327 return;
apps/web/src/routes/__root.tsx+9−3
@@ -8,6 +8,7 @@ import { useNativeApi } from "../hooks/useNativeApi"; · 7 unmodified lines
88 import { invalidateGitQueries } from "../lib/gitReactQuery";
99 import { DEFAULT_MODEL } from "../model-logic";
1010 import { useStore } from "../store";
11+import { createThread } from "../threadFactory";
1112 import { onServerWelcome } from "../wsNativeApi";
1213
1314 export const Route = createRootRouteWithContext<{
@@ -50,6 +51,8 @@ function EventRouter() { · 36 unmodified lines
5051 strict: false,
5152 select: (params) => params.threadId,
5253 });
54+ const activeThreadIdRef = useRef(activeThreadId);
55+ activeThreadIdRef.current = activeThreadId;
5356
5457 useEffect(() => {
5558 if (!api) return;
@@ -57,15 +60,14 @@ function EventRouter() { · 1 unmodified line
5760 if (event.method === "turn/completed") {
5861 void invalidateGitQueries(queryClient);
5962 }
60− if (!activeThreadId) return;
6163 dispatch({
6264 type: "APPLY_EVENT",
6365 event,
6466 activeAssistantItemRef,
65− activeThreadId,
67+ activeThreadId: activeThreadIdRef.current,
6668 });
6769 });
68− }, [activeThreadId, api, dispatch, queryClient]);
70+ }, [api, dispatch, queryClient]);
6971
7072 useEffect(() => {
7173 if (!api) return;
@@ -115,6 +117,10 @@ function AutoProjectBootstrap() { · 43 unmodified lines
115117 scripts: [],
116118 },
117119 });
120+ dispatch({
121+ type: "ADD_THREAD",
122+ thread: createThread(projectId),
123+ });
118124 dispatch({ type: "SET_THREADS_HYDRATED", hydrated: true });
119125 });
120126 }, [state.projects, dispatch]);
apps/web/src/routes/_chat.$threadId.tsx+4−2
@@ -5,7 +5,7 @@ import ChatView from "../components/ChatView"; · 4 unmodified lines
55 import { useStore } from "../store";
66
77 function ChatThreadRouteView() {
8− const { state } = useStore();
8+ const { state, dispatch } = useStore();
99 const navigate = useNavigate();
1010 const { threadId } = Route.useParams();
1111 const threadExists = threadId ? state.threads.some((thread) => thread.id === threadId) : false;
@@ -24,7 +24,9 @@ function ChatThreadRouteView() { · 12 unmodified lines
2424 void navigate({ to: "/", replace: true });
2525 return;
2626 }
27− }, [navigate, state.threadsHydrated, threadExists, threadId]);
27+
28+ dispatch({ type: "VISIT_THREAD", threadId });
29+ }, [dispatch, navigate, state.threadsHydrated, threadExists, threadId]);
2830
2931 if (!threadId || !state.threadsHydrated || !threadExists) {
3032 return null;
apps/web/src/store.ts+11−1
@@ -67,7 +67,8 @@ type Action = · 66 unmodified lines
6767 worktreePath: string | null;
6868 }
6969 | { type: "SET_RUNTIME_MODE"; mode: RuntimeMode }
70− | { type: "DELETE_THREAD"; threadId: string };
70+ | { type: "DELETE_THREAD"; threadId: string }
71+ | { type: "VISIT_THREAD"; threadId: string };
7172
7273 // ── State ────────────────────────────────────────────────────────────
7374
@@ -849,6 +850,15 @@ export function reducer(state: AppState, action: Action): AppState { · 775 unmodified lines
849850 runtimeMode: action.mode,
850851 };
851852
853+ case "VISIT_THREAD":
854+ return {
855+ ...state,
856+ threads: updateThread(state.threads, action.threadId, (t) => ({
857+ ...t,
858+ lastVisitedAt: new Date().toISOString(),
859+ })),
860+ };
861+
852862 case "DELETE_THREAD":
853863 return { ...state, threads: state.threads.filter((t) => t.id !== action.threadId) };
854864
apps/web/vite.config.js+0−41
@@ -1,41 +0,0 @@
1−import tailwindcss from "@tailwindcss/vite";
2−import react from "@vitejs/plugin-react";
3−import { tanstackRouter } from "@tanstack/router-plugin/vite";
4−import { defineConfig } from "vite";
5−const port = Number(process.env.PORT ?? 5173);
6−export default defineConfig({
7− plugins: [
8− tanstackRouter(),
9− react({
10− babel: {
11− plugins: [["babel-plugin-react-compiler", { target: "19" }]],
12− },
13− }),
14− tailwindcss(),
15− ],
16− define: {
17− // In dev mode, tell the web app where the WebSocket server lives
18− "import.meta.env.VITE_WS_URL": JSON.stringify(process.env.VITE_WS_URL ?? ""),
19− },
20− experimental: {
21− enableNativePlugin: true,
22− },
23− resolve: {
24− tsconfigPaths: true,
25− },
26− server: {
27− port,
28− strictPort: true,
29− hmr: {
30− // Explicit config so Vite's HMR WebSocket connects reliably
31− // inside Electron's BrowserWindow. Vite 8 uses console.debug for
32− // connection logs — enable "Verbose" in DevTools to see them.
33− protocol: "ws",
34− host: "localhost",
35− },
36− },
37− build: {
38− outDir: "dist",
39− emptyOutDir: true,
40− },
41−});

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t