refactor(canvas): review fixes for the busy flag, the comparison note and the bundle size check

- busyReason had no reader: the chat engine sets isBusy through the store's
  generic set, like every other flag.
- The isSameDocument comment says the two "same document" rules disagree
  in both directions, so neither is a subset of the other.
- The import boundary test bundles minified with one pako (the MCP core
  resolves its own copy) and caps the canvas core at 160 KB (134 KB now).
This commit is contained in:
dayuan.jiang
2026-10-11 20:56:07 +09:00
parent 48c7e43521
commit 6e375fb963
4 changed files with 17 additions and 9 deletions
+1 -1
View File
@@ -729,7 +729,7 @@ export function ChatEngineProvider({
// Canvas components (version cards, the compare dialog, "ask AI") read
// the busy flag from the canvas store
useEffect(() => {
useCanvasStore.getState().setBusy(isBusy ? "chat" : null)
useCanvasStore.getState().set({ isBusy })
}, [isBusy])
// The page goes (another language mounts a new one): the answer stops,
// so it cannot reach the next page's canvas
+4 -1
View File
@@ -288,7 +288,10 @@ export function sameFileVars(a: string | null, b: string | null): boolean {
* page settings and file variables. This is the web app's one rule for "is
* this version on the canvas" (versions, compare, one-step commits). The
* MCP server's edit gate has its own (contentFingerprint): it compares the
* cells as written, and leaves page settings and file variables out.
* cells as written, and leaves page settings and file variables out. The
* two disagree both ways (a background change is a difference only here;
* a geometry written "40.0" and "40" is one only there), so neither is a
* subset of the other.
*/
export function isSameDocument(a: string, b: string): boolean {
const pagesOf = (doc: Document) => {
+1 -6
View File
@@ -33,10 +33,7 @@ interface CanvasState {
/** Something is changing the canvas (an answer streams in, a sync
* runs): version actions and "ask AI" wait */
isBusy: boolean
/** Who set the busy flag ("chat", "sync"); null when not busy */
busyReason: string | null
set: (partial: Partial<Omit<CanvasState, "set" | "setBusy">>) => void
setBusy: (reason: string | null) => void
set: (partial: Partial<Omit<CanvasState, "set">>) => void
}
export const useCanvasStore = create<CanvasState>((set) => ({
@@ -48,7 +45,5 @@ export const useCanvasStore = create<CanvasState>((set) => ({
isFreehand: false,
isDrawioPopupOpen: false,
isBusy: false,
busyReason: null,
set: (partial) => set(partial),
setBusy: (reason) => set({ isBusy: reason !== null, busyReason: reason }),
}))
+11 -1
View File
@@ -122,8 +122,11 @@ describe("canvas import boundary", () => {
platform: "browser",
format: "esm",
jsx: "automatic",
alias: { "@": root },
// The MCP core (packages/mcp-server/src) resolves pako from its
// own node_modules; one copy, as the shell build must do
alias: { "@": root, pako: path.join(root, "node_modules/pako") },
external: ["react", "react-dom", "next/*"],
minify: true,
logLevel: "silent",
})
const inputs = result.metafile.inputs
@@ -145,5 +148,12 @@ describe("canvas import boundary", () => {
.map((specifier) => `${file} -> ${specifier}`),
)
expect(nextImports).toEqual([])
// The canvas core stays small enough for the MCP's browser shell
// (134 KB minified, pako once)
const stage = Object.values(result.metafile.outputs).find(
(output) =>
output.entryPoint === "components/canvas/canvas-stage.tsx",
)
expect(stage?.bytes).toBeLessThan(160 * 1024)
})
})