fix: Z39 isolate module data by slug
Keep agent-bound modules in per-app schemas and prevent stale frontend routes from displaying another module's blueprint or rows. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
2
web/dist/index.html
vendored
2
web/dist/index.html
vendored
@@ -7,7 +7,7 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,700&family=Manrope:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<script type="module" crossorigin src="/assets/index-aOZIXFeC.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-Pe8anoMp.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CtqUfnO-.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -93,6 +93,7 @@ export default function App() {
|
||||
const excelInputRef = useRef<HTMLInputElement>(null);
|
||||
const imagesInputRef = useRef<HTMLInputElement>(null);
|
||||
const layoutsInputRef = useRef<HTMLInputElement>(null);
|
||||
const routeRequestRef = useRef(0);
|
||||
const [storageMode, setStorageMode] = useState<"schema_per_app" | "database_per_app">("schema_per_app");
|
||||
const [llmProvider, setLlmProvider] = useState("deepseek");
|
||||
const [llmModel, setLlmModel] = useState("deepseek-chat");
|
||||
@@ -226,22 +227,25 @@ export default function App() {
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
async function syncRoute() {
|
||||
const requestID = ++routeRequestRef.current;
|
||||
const isStale = () => cancelled || requestID !== routeRequestRef.current;
|
||||
const pid = previewRouteId();
|
||||
if (pid) {
|
||||
if (!cancelled) {
|
||||
if (!isStale()) {
|
||||
setBusy(true);
|
||||
setError("");
|
||||
setBlueprint(null);
|
||||
}
|
||||
try {
|
||||
const pack = await fetchPreview(pid);
|
||||
if (cancelled) return;
|
||||
if (isStale()) return;
|
||||
setBlueprint(pack.blueprint);
|
||||
setDraft(pack.blueprint);
|
||||
setSlug(pack.blueprint?.meta?.slug || "preview");
|
||||
setResource(pack.resource || "records");
|
||||
setView("app");
|
||||
} catch (e: any) {
|
||||
if (!cancelled) {
|
||||
if (!isStale()) {
|
||||
clearActivePreview();
|
||||
setError(e.message || String(e));
|
||||
setView("console");
|
||||
@@ -249,7 +253,7 @@ export default function App() {
|
||||
navigateToConsole();
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) setBusy(false);
|
||||
if (!isStale()) setBusy(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -257,41 +261,46 @@ export default function App() {
|
||||
clearActivePreview();
|
||||
const routeSlug = appRouteSlug();
|
||||
if (!routeSlug) {
|
||||
if (!cancelled) {
|
||||
if (!isStale()) {
|
||||
setView("console");
|
||||
setBlueprint(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!cancelled) {
|
||||
if (!isStale()) {
|
||||
setBusy(true);
|
||||
setError("");
|
||||
setSlug(routeSlug);
|
||||
setBlueprint(null);
|
||||
}
|
||||
try {
|
||||
const sess = session || loadSession();
|
||||
const bp = await getBlueprint(sess, routeSlug);
|
||||
if (cancelled) return;
|
||||
if (isStale()) return;
|
||||
if (String(bp?.meta?.slug || "") !== routeSlug) {
|
||||
throw new Error(`模块路由不匹配:请求 ${routeSlug},返回 ${String(bp?.meta?.slug || "空")}`);
|
||||
}
|
||||
setBlueprint(bp);
|
||||
setDraft(bp);
|
||||
const r0 = bp?.apis?.resources?.[0]?.path?.replace(/^\//, "") || resource;
|
||||
setResource(r0);
|
||||
setView("app");
|
||||
} catch (e: any) {
|
||||
if (!cancelled) {
|
||||
if (!isStale()) {
|
||||
setError(e.message || String(e));
|
||||
setView("console");
|
||||
setBlueprint(null);
|
||||
navigateToConsole();
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) setBusy(false);
|
||||
if (!isStale()) setBusy(false);
|
||||
}
|
||||
}
|
||||
void syncRoute();
|
||||
window.addEventListener("hashchange", syncRoute);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
routeRequestRef.current++;
|
||||
window.removeEventListener("hashchange", syncRoute);
|
||||
};
|
||||
}, [session]);
|
||||
@@ -576,6 +585,7 @@ export default function App() {
|
||||
if (view === "app" && blueprint) {
|
||||
return (
|
||||
<GeneratedApp
|
||||
key={String(blueprint?.meta?.slug || slug)}
|
||||
session={session}
|
||||
blueprint={blueprint}
|
||||
onBack={() => {
|
||||
|
||||
@@ -352,12 +352,13 @@ export async function getBlueprint(session: Session | null | undefined, slug: st
|
||||
if (session?.accessToken) {
|
||||
const res = await apiFetch(`${PLATFORM}/api/v1/apps/${slug}/blueprint`, {
|
||||
headers: { Authorization: `Bearer ${session.accessToken}` },
|
||||
cache: "no-store",
|
||||
});
|
||||
const data = await readJson(res);
|
||||
throwIfBad(res, data, "blueprint failed");
|
||||
return data;
|
||||
}
|
||||
const res = await apiFetch(`${PLATFORM}/api/v1/public/apps/${slug}/blueprint`);
|
||||
const res = await apiFetch(`${PLATFORM}/api/v1/public/apps/${slug}/blueprint`, { cache: "no-store" });
|
||||
const data = await readJson(res);
|
||||
throwIfBad(res, data, "blueprint failed");
|
||||
return data;
|
||||
@@ -400,7 +401,7 @@ export async function listRows(
|
||||
}
|
||||
const headers: Record<string, string> = {};
|
||||
if (session?.accessToken) headers.Authorization = `Bearer ${session.accessToken}`;
|
||||
const res = await apiFetch(path, { headers });
|
||||
const res = await apiFetch(path, { headers, cache: "no-store" });
|
||||
const data = await readJson(res);
|
||||
throwIfBad(res, data, "list failed");
|
||||
return data as { items: Record<string, unknown>[]; total: number };
|
||||
|
||||
Reference in New Issue
Block a user