Compare commits

...

2 Commits

Author SHA1 Message Date
Naiyuan Qing
ed7a288946 fix(web): prevent 404 on workspace switch and downgrade 404 log level
- Skip issue refetch when store is cleared during workspace switch by
  tracking which issue was already loaded (loadedIdRef pattern)
- Downgrade 404 responses from logger.error to logger.warn in ApiClient
  since resource-not-found is a normal business response, not a bug

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:26:53 +08:00
Jiayuan
b1f7364097 fix(web): navigate to /issues when switching workspaces
When switching workspaces while on a detail page (e.g. /issues/[id]),
the store clears old data and the page tries to fetch the old resource
with the new workspace context, causing a 404 error. Navigate to the
issues list before switching to avoid referencing stale resources.
2026-04-02 00:15:47 +08:00
4 changed files with 17 additions and 2 deletions

View File

@@ -132,6 +132,7 @@ export function AppSidebar() {
key={ws.id}
onClick={() => {
if (ws.id !== workspace?.id) {
router.push("/issues");
switchWorkspace(ws.id);
}
}}

View File

@@ -196,12 +196,22 @@ export function IssueDetail({ issueId, onDelete, defaultSidebarOpen = true, layo
const issue = useIssueStore((s) => s.issues.find((i) => i.id === id)) ?? null;
const [issueLoading, setIssueLoading] = useState(!issue);
// If issue isn't in the store yet, fetch and upsert it
// If issue isn't in the store yet, fetch and upsert it.
// loadedIdRef tracks which issue was already loaded — if it disappears
// from the store (workspace switch clears all issues), skip refetch.
const loadedIdRef = useRef<string | null>(null);
useEffect(() => {
if (issue) {
loadedIdRef.current = id;
setIssueLoading(false);
return;
}
// Issue was loaded for this id but vanished → store cleared (workspace switch)
if (loadedIdRef.current === id) {
loadedIdRef.current = null;
return;
}
// Issue not in store → fetch it
setIssueLoading(true);
api
.getIssue(id)

View File

@@ -1,6 +1,7 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { ArrowLeft } from "lucide-react";
import { Input } from "@/components/ui/input";
@@ -18,6 +19,7 @@ import { useWorkspaceStore } from "@/features/workspace";
const SLUG_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
export function CreateWorkspaceModal({ onClose }: { onClose: () => void }) {
const router = useRouter();
const [name, setName] = useState("");
const [slug, setSlug] = useState("");
const [creating, setCreating] = useState(false);
@@ -50,6 +52,7 @@ export function CreateWorkspaceModal({ onClose }: { onClose: () => void }) {
slug: slug.trim(),
});
onClose();
router.push("/issues");
await switchWorkspace(ws.id);
} catch {
toast.error("Failed to create workspace");

View File

@@ -113,7 +113,8 @@ export class ApiClient {
if (!res.ok) {
if (res.status === 401) this.handleUnauthorized();
const message = await this.parseErrorMessage(res, `API error: ${res.status} ${res.statusText}`);
this.logger.error(`${res.status} ${path}`, { rid, duration: `${Date.now() - start}ms`, error: message });
const logLevel = res.status === 404 ? "warn" : "error";
this.logger[logLevel](`${res.status} ${path}`, { rid, duration: `${Date.now() - start}ms`, error: message });
throw new Error(message);
}