Merge feature/ticket79-flaky-permissions-test into develop

Ticket #79 : fix du flake PermissionsPanel > saves project defaults
(PermissionEditor draft resync clobbering in-flight edits). QA vert sur le
périmètre du ticket (2/2 stable + suite complète non-shuffled) ; le rouge
observé en shuffle vient d'un autre test préexistant sans rapport
(setCellAgent.test.tsx, tracé séparément en #85).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 14:57:44 +02:00

View File

@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { Button, Panel, Spinner, cn } from "@/shared";
import type { PermissionSet, PermissionPosture } from "@/domain";
@ -232,9 +232,26 @@ function PermissionEditor({
onClear,
}: PermissionEditorProps) {
const [local, setLocal] = useState<PolicyDraft>(draft);
// `draft` is a NEW object every time the underlying document changes for ANY
// reason (initial load landing after mount, a Refresh, another tab's save…),
// even when its content matches what is already in `local`. Naively
// re-syncing on every `draft` change is a genuine race: if it fires after the
// user has started editing (in production: a slow initial fetch landing
// right as the user types; in tests: a passive effect flushing later than a
// synchronous `fireEvent` sequence — ticket #79 flake), it silently discards
// the user's in-progress edit. `prevDraftRef` tracks the last draft this
// effect adopted `local` from; we only re-sync when `local` still matches it
// (nothing has been edited since), so a legitimate incoming draft (first
// load, or the echo of the user's own just-saved edit) is adopted, but an
// edit in flight never gets clobbered.
const prevDraftRef = useRef(draft);
useEffect(() => {
setLocal(draft);
setLocal((current) =>
JSON.stringify(current) === JSON.stringify(prevDraftRef.current)
? draft
: current,
);
prevDraftRef.current = draft;
}, [draft]);
const changed = useMemo(