Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ export default defineConfig([
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
// Omit-via-rest (`const { drop, ...keep } = obj`) is the intended pattern.
'@typescript-eslint/no-unused-vars': ['error', { ignoreRestSiblings: true }],
// Syntax the compiler cannot lower, so the whole hook silently loses its memoization.
// Syntax the compiler cannot lower, so the enclosing component silently loses its memoization.
'react-hooks/todo': 'error',
},
},
{
files: ['**/*.test.ts', '**/*.test.tsx', 'src/test/**', 'tests/**/*.ts', 'e2e/**/*.ts'],
files: ['**/*.test.ts', '**/*.test.tsx', 'src/test/**', 'tests/**/*.ts'],
rules: {
'@typescript-eslint/no-non-null-assertion': 'off',
},
Expand Down
8 changes: 6 additions & 2 deletions src/components/Canvas/LabelCanvas.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { tryFinally } from "../../lib/tryFinally";
import {
forwardRef,
useImperativeHandle,
Expand Down Expand Up @@ -36,6 +35,7 @@ import { registerBarcodeWidthProber, unregisterBarcodeWidthProber, type ProbeCtx
import { resolveForMeasure } from "@zplab/core/lib/barcodeDims";
import { measureBarcodeFootprintDots } from "./bwipHelpers";
import { copyText } from "../../lib/clipboard";
import { tryFinally } from "../../lib/tryFinally";
import { selectTidyTargets } from "../../lib/tidyClassify";
import { safeAreaRectDots } from "../../lib/safeArea";
import { measuredBoundsMap, subscribeMeasuredBounds, getMeasuredSnapshot } from "../../lib/measuredBoundsCache";
Expand Down Expand Up @@ -1828,7 +1828,11 @@ export const LabelCanvas = forwardRef<LabelCanvasHandle, Props>(function LabelCa
}}
onTransform={onTransform}
boundBoxFunc={boundBoxFunc}
onTransformEnd={() => tryFinally(onTransformEnd, () => { transformActiveRef.current = false; })}
onTransformEnd={() =>
tryFinally(onTransformEnd, () => {
transformActiveRef.current = false;
})
}
borderStroke={colors.selection}
anchorStroke={colors.selection}
anchorFill="#ffffff"
Expand Down
2 changes: 1 addition & 1 deletion src/components/Canvas/hooks/useKonvaTransformer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { editBounds, moduleScaledProps, wireBounds, type Range } from "@zplab/core/types/propSpec";
import { tryFinally } from "../../../lib/tryFinally";
import { useRef, useEffect, useLayoutEffect } from "react";
import { flushSync } from "react-dom";
import type Konva from "konva";
Expand Down Expand Up @@ -49,6 +48,7 @@ import { projectedAnchorXDots, projectMultiResize } from "../../../lib/multiResi
import { lineHandlesNodeId, lineRootNodeId } from "../konvaObjectProps";
import { isAxisSwapped, objectRotation } from "@zplab/core/registry/rotation";
import { getMeasuredSnapshot } from "../../../lib/measuredBoundsCache";
import { tryFinally } from "../../../lib/tryFinally";
import {
computeResizeSnap,
deriveActiveEdges,
Expand Down
7 changes: 3 additions & 4 deletions src/hooks/useMcpBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ import {
} from "../lib/mcpServer";
import { isDesktopShell } from "../lib/platform";

// Module level, since the compiler cannot lower an import expression inside the hook.
const tauriEvent = () => import("@tauri-apps/api/event");
const tauriCore = () => import("@tauri-apps/api/core");
// Hoisted: an import expression inside the hook is a compiler bailout.
const loadTauri = () => Promise.all([import("@tauri-apps/api/event"), import("@tauri-apps/api/core")]);

/** Register the sidecar listeners, then have Rust flush the events it
* queued while none existed (see event_buffer in mcp.rs). */
Expand All @@ -28,7 +27,7 @@ export function useMcpBridge(): void {
// teardown withdraws its own and never the one that replaced it.
let attachedSession: string | null = null;
void (async () => {
const [{ listen }, { invoke }] = await Promise.all([tauriEvent(), tauriCore()]);
const [{ listen }, { invoke }] = await loadTauri();
const fns = await Promise.all([
listen<[number, string]>("mcp://open-draft", (e) => {
void respondToOpenDraft(e.payload[0], e.payload[1]).catch(() => undefined);
Expand Down
12 changes: 6 additions & 6 deletions src/hooks/useNativeMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ interface InstalledMenu {
let latest: MenuData | null = null;
let installed: InstalledMenu | null = null;
let updateGen = 0;
// Hoisted: ++ on a module variable inside the hook is a compiler bailout.
const bumpUpdateGen = (): number => ++updateGen;
let queue: Promise<void> = Promise.resolve();
// A function, since the compiler cannot lower ++ on a module variable inside the hook.
const nextUpdateGen = (): number => ++updateGen;
/** The OS toggles a clicked CheckMenuItem's checkmark itself; after a jump we
* re-assert every step's checkmark so that stray toggle can't linger. */
let checkmarkDirty = false;
Expand All @@ -106,7 +106,7 @@ if (import.meta.hot) {
import.meta.hot.dispose(() => {
// Invalidate queued rebuilds and drain the in-flight update before closing,
// so a mid-await patch/rebuild can't touch freed handles.
nextUpdateGen();
bumpUpdateGen();
queue = queue.then(() => {
if (installed) closeAll(installed.resources);
installed = null;
Expand Down Expand Up @@ -386,7 +386,7 @@ async function patchMenu(inst: InstalledMenu, d: MenuData): Promise<void> {
// for the next unrelated signature change.
if (installed) closeAll(installed.resources);
installed = null;
const gen = nextUpdateGen();
const gen = bumpUpdateGen();
queue = queue.then(() => runUpdate(gen)).catch(() => undefined);
}
}
Expand Down Expand Up @@ -436,12 +436,12 @@ export function useNativeMenu(

useEffect(() => {
if (!isDesktopShell) return;
const gen = nextUpdateGen();
const gen = bumpUpdateGen();
queue = queue.then(() => runUpdate(gen)).catch(() => undefined);
// Invalidate a queued-but-unstarted update on re-run/unmount (StrictMode
// remounts included); the re-run queues a fresh one.
return () => {
nextUpdateGen();
bumpUpdateGen();
};
}, [signature]);
}
4 changes: 2 additions & 2 deletions src/lib/tryFinally.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/** The React Compiler cannot lower a try/finally inside a hook, so the finally lives here.
* `body` is a closure, so a return inside it leaves only the closure. */
/** Keeps the finally out of a component or hook, where the React Compiler cannot lower it.
* A `return` inside `body` leaves `body`, not the caller. */
export function tryFinally(body: () => void, cleanup: () => void): void {
try {
body();
Expand Down
Loading