From 5fcaa85d8ffffb5979f09a9e502eb8c50a5dc99b Mon Sep 17 00:00:00 2001 From: IliesAllali Date: Thu, 1 Oct 2026 11:12:27 +0200 Subject: [PATCH] =?UTF-8?q?Front=20:=20mineures=20et=20correctifs=20group?= =?UTF-8?q?=C3=A9s=20(Dependabot=20#31),=20refs=20hors=20du=20rendu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit React et react-dom 19.3.0, @types/react et @types/react-dom 19.3.0, posthog-js 1.434.15, autoprefixer 10.6.1, eslint-plugin-react-hooks 7.1.1, eslint-plugin-react-refresh 0.5.7. La règle react-hooks/refs de la 7.1 voit maintenant les refs lus ou écrits pendant le rendu, corrigés plutôt que désactivés : le nom de l'arbre partagé affiché par l'écran compte passe par un état (un ref ne redessine rien), la personne de départ de l'arbre est recopiée dans un effet déclaré avant celui du cadrage, l'API de zoom est exposée par useImperativeHandle, l'horodatage des portraits est posé au premier chargement au lieu de Date.now() au rendu (react-hooks/purity). react-refresh 0.5 garde l'export par défaut et la config vite utilisés ici. Co-Authored-By: Claude Opus 5.5 --- frontend/package-lock.json | 122 +++++++++++++++------------- frontend/package.json | 16 ++-- frontend/src/App.jsx | 6 +- frontend/src/components/Galaxy.jsx | 87 ++++++++++---------- frontend/src/hooks/useTreeAccess.js | 7 +- 5 files changed, 125 insertions(+), 113 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 158f7e2..f2a998f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,19 +9,19 @@ "version": "0.0.0", "dependencies": { "lucide-react": "^0.564.0", - "posthog-js": "^1.353.0", - "react": "^19.2.0", - "react-dom": "^19.2.0" + "posthog-js": "^1.435.6", + "react": "^19.3.0", + "react-dom": "^19.3.0" }, "devDependencies": { "@eslint/js": "^9.39.1", - "@types/react": "^19.2.7", - "@types/react-dom": "^19.2.3", + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", "@vitejs/plugin-react": "^5.1.1", - "autoprefixer": "^10.4.24", + "autoprefixer": "^10.6.1", "eslint": "^9.39.1", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-react-refresh": "^0.4.24", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.7", "globals": "^16.5.0", "postcss": "^8.5.6", "tailwindcss": "^3.4.19", @@ -1070,28 +1070,28 @@ } }, "node_modules/@posthog/browser-common": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@posthog/browser-common/-/browser-common-0.9.0.tgz", - "integrity": "sha512-EWoVzs1vVY7ihpn8yOjj3jyjriHUEEMVSBNL/TW3hk09fTriWRe76vJXrRg0G0yjTYxHmtlSkNn4mOV24ZEzzA==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@posthog/browser-common/-/browser-common-0.9.1.tgz", + "integrity": "sha512-Dl8uV2OZAxfHfWyC7K0Y3HtT7ld+WGLbXIwgCgAGziOMGDIa/bepCRrCxy7IxsEzqY1HOrq/VDdcoNN7hBTSQQ==", "license": "MIT", "dependencies": { - "@posthog/core": "^1.54.2", - "@posthog/types": "^1.412.1" + "@posthog/core": "^1.55.3", + "@posthog/types": "^1.413.0" } }, "node_modules/@posthog/core": { - "version": "1.55.2", - "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.55.2.tgz", - "integrity": "sha512-2mYxGbDmTLUs096VbcZqSWo0lrI48/BrX65r54NynxxFq409BOx9w73HxOAtOKZ1vblkdV6/GxQTe7KDcIQidg==", + "version": "1.55.3", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.55.3.tgz", + "integrity": "sha512-XnmG9jo7WlxTeN4oCNWH4ESLQHR6ZPZ+mcq+8JxU62k5i1BAe3BSrybnmXHjqKp7e3rUvLK2bOzGZGSlfHvlNA==", "license": "MIT", "dependencies": { - "@posthog/types": "^1.412.4" + "@posthog/types": "^1.413.0" } }, "node_modules/@posthog/types": { - "version": "1.412.4", - "resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.412.4.tgz", - "integrity": "sha512-Q7lV9O9TbLngjOYw1ucm3bS3tn48xb1B4ZQdDy8gv7yfKe99hCTeWYG4xZ36nJM80VxpTF9TriiJt5hngDOkBg==", + "version": "1.413.0", + "resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.413.0.tgz", + "integrity": "sha512-tYAVzNojWckuG/wxBB0fwE70sFruQQq4xbhkci7eH2ARU3mMfUTkvpN1HwBMpm0AtbxidLiH0IFvagmdJ8opPw==", "license": "MIT" }, "node_modules/@rolldown/pluginutils": { @@ -1530,21 +1530,23 @@ "dev": true }, "node_modules/@types/react": { - "version": "19.2.13", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", - "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", "devOptional": true, + "license": "MIT", "dependencies": { "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", "dev": true, + "license": "MIT", "peerDependencies": { - "@types/react": "^19.2.0" + "@types/react": "^19.3.0" } }, "node_modules/@types/trusted-types": { @@ -1795,9 +1797,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.24", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz", - "integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==", + "version": "10.6.1", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.6.1.tgz", + "integrity": "sha512-cL1Qz6ADZhcEbny/8HPfe99J6HhNoYtpX2LFLIbhgGE7Q1hlQVkYFdetDN7Id3KiQxhDrHwzlHr/YQCnZ8+xSA==", "dev": true, "funding": [ { @@ -1813,9 +1815,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001766", + "browserslist": "^4.28.9", + "caniuse-lite": "^1.0.30001810", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -2286,10 +2289,11 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", - "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", @@ -2301,16 +2305,17 @@ "node": ">=18" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.26", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", - "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.7.tgz", + "integrity": "sha512-XhJSzLljuYD4UjNuFGJu2v7aD3sqNVK12w5/DCUfrNfHWegZo2+TsavNx8MuxMwEquuFAzNbNfXKU0WoZ+YUIg==", "dev": true, + "license": "MIT", "peerDependencies": { - "eslint": ">=8.40" + "eslint": "^9 || ^10" } }, "node_modules/eslint-scope": { @@ -3356,14 +3361,14 @@ "dev": true }, "node_modules/posthog-js": { - "version": "1.434.12", - "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.434.12.tgz", - "integrity": "sha512-gD7Xv8/iuSGiYYQHB9fxnfTsiPvi+WNJiEW9OXnZ/UFS9ZVkOzMVmEq29/NFLeYebVGQpVCBh0H4xbK1sJ7j5g==", + "version": "1.435.6", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.435.6.tgz", + "integrity": "sha512-PdBZw1Np4Ch2ZzvQ/Ij0CTBrJSkSeEmDJsyJfbaipTYH8XJKJk45BYLtNgL4z9DlevRcYJxhwb15VrDzbw7shA==", "license": "(Apache-2.0 AND MIT)", "dependencies": { - "@posthog/browser-common": "^0.9.0", - "@posthog/core": "^1.55.2", - "@posthog/types": "^1.412.4", + "@posthog/browser-common": "^0.9.1", + "@posthog/core": "^1.55.3", + "@posthog/types": "^1.413.0", "core-js": "^3.49.0", "dompurify": "^3.4.13", "fflate": "^0.4.8", @@ -3448,22 +3453,24 @@ ] }, "node_modules/react": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", - "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", + "license": "MIT", "dependencies": { - "scheduler": "^0.27.0" + "scheduler": "^0.28.0" }, "peerDependencies": { - "react": "^19.2.4" + "react": "^19.3.0" } }, "node_modules/react-refresh": { @@ -3618,9 +3625,10 @@ } }, "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==" + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", diff --git a/frontend/package.json b/frontend/package.json index 5de3475..3abb110 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,19 +13,19 @@ }, "dependencies": { "lucide-react": "^0.564.0", - "posthog-js": "^1.353.0", - "react": "^19.2.0", - "react-dom": "^19.2.0" + "posthog-js": "^1.435.6", + "react": "^19.3.0", + "react-dom": "^19.3.0" }, "devDependencies": { "@eslint/js": "^9.39.1", - "@types/react": "^19.2.7", - "@types/react-dom": "^19.2.3", + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", "@vitejs/plugin-react": "^5.1.1", - "autoprefixer": "^10.4.24", + "autoprefixer": "^10.6.1", "eslint": "^9.39.1", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-react-refresh": "^0.4.24", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.7", "globals": "^16.5.0", "postcss": "^8.5.6", "tailwindcss": "^3.4.19", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 4b29553..e58af4c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -436,6 +436,8 @@ function App() { useEffect(() => { setBooting(tree.bootState === 'loading') }, [tree.bootState]) // Arbre partagé d'où l'on vient quand on ouvre l'écran compte : on y revient après connexion/création const pendingSharedTreeRef = useRef(null) + // Son nom, affiché par l'écran compte : un ref lu au rendu ne redessine rien quand il change + const [pendingSharedTreeName, setPendingSharedTreeName] = useState('') // Écran de compte ouvert depuis un arbre affiché : la croix y ramène const [accountFromTree, setAccountFromTree] = useState(false) const refreshTreeAndKeepSelection = useCallback(async (personId, options = {}) => { @@ -2064,6 +2066,7 @@ function App() { treeOwnerEmail: ctx.treeOwnerEmail, } : null + setPendingSharedTreeName(pendingSharedTreeRef.current?.treeName || '') setTreeNotFound(false) tree.setGateError('') auth.setAccountError('') @@ -2130,6 +2133,7 @@ function App() { return true } finally { pendingSharedTreeRef.current = null + setPendingSharedTreeName('') } } @@ -2456,7 +2460,7 @@ function App() { trees={auth.accountTrees} activeTreeId={tree.treeContext.treeId} defaultMode={accountEntryMode} - pendingTreeName={pendingSharedTreeRef.current?.treeName || ''} + pendingTreeName={pendingSharedTreeName} onLogin={handleLogin} onRegister={handleRegister} onOpenTree={handleOpenTreeFromAccount} diff --git a/frontend/src/components/Galaxy.jsx b/frontend/src/components/Galaxy.jsx index 18f3c23..816ce80 100644 --- a/frontend/src/components/Galaxy.jsx +++ b/frontend/src/components/Galaxy.jsx @@ -1,4 +1,4 @@ -import { memo, useEffect, useRef, useState, useCallback } from 'react' +import { memo, useEffect, useImperativeHandle, useRef, useState, useCallback } from 'react' import { getPersonMedias, getParents, getChildren, getPersonUnions } from '../data/mockData' import { PERSON_R, FLOAT_SPEED, FLOAT_AMPLITUDE, REPULSION_RADIUS, REPULSION_STRENGTH, MAX_ORBIT_MEDIAS, ENTRANCE_GEN_DELAY, ENTRANCE_NODE_DURATION, ENTRANCE_NODE_STAGGER, ENTRANCE_MAX_GEN_SPAN, ENTRANCE_MAX_NODE_SPAN, ENTRANCE_LINE_DELAY, ENTRANCE_LINE_DURATION, ENTRANCE_ORBIT_DELAY, ENTRANCE_ORBIT_DURATION, ENTRANCE_ORBIT_STAGGER } from './galaxy/constants' import { easeOutCubic } from './galaxy/utils' @@ -33,7 +33,8 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig const canvasRef = useRef(null) // Lu au calcul du cadrage seulement : changer de personne de départ ne recadre pas une vue en cours const focusPersonIdRef = useRef(focusPersonId) - focusPersonIdRef.current = focusPersonId + // Mis à jour hors du rendu, avant l'effet de cadrage déclaré plus bas (même phase, ordre de déclaration) + useEffect(() => { focusPersonIdRef.current = focusPersonId }, [focusPersonId]) const layoutDataRef = useRef(null) const [layoutReady, setLayoutReady] = useState(false) const transformRef = useRef({ x: 0, y: 0, scale: 1 }) @@ -118,48 +119,46 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig maxScale: MAX_SCALE, }) - // Expose zoom API to parent (ZoomControl component) - if (zoomApiRef) { - zoomApiRef.current = { - zoomTo: (newScale) => { - const canvas = canvasRef.current - if (!canvas) return - const rect = canvas.getBoundingClientRect() - const cx = rect.width / 2 - const cy = rect.height / 2 - const base = targetTransformRef.current - const clamped = Math.min(MAX_SCALE, Math.max(MIN_SCALE, newScale)) - const ratio = clamped / base.scale - targetTransformRef.current = { - scale: clamped, - x: cx - ratio * (cx - base.x), - y: cy - ratio * (cy - base.y), - } - }, - resetZoom: () => { - if (initialTransformRef.current) { - targetTransformRef.current = { ...initialTransformRef.current } - } - }, - // Recherche : glisse jusqu'à la personne, dans la partie de l'écran que la fiche ne couvre pas - // (fiche à gauche sur ordinateur, 408 px ; par le bas sur téléphone) - focusPerson: (personId) => { - const canvas = canvasRef.current - const node = layoutDataRef.current?.children.find(n => n.id === `p-${personId}`) - if (!canvas || !node) return - const rect = canvas.getBoundingClientRect() - const pos = getAnimatedPos(node, performance.now()) - const scale = Math.min(MAX_SCALE, Math.max(targetTransformRef.current.scale, 1)) - const phone = rect.width <= 768 - const screenX = phone ? rect.width / 2 : (408 + rect.width) / 2 - const screenY = phone ? rect.height * 0.22 : rect.height / 2 - targetTransformRef.current = { scale, x: screenX - pos.cx * scale, y: screenY - pos.cy * scale } - }, - getScale: () => transformRef.current.scale, - MIN_SCALE, - MAX_SCALE, - } - } + // Expose zoom API to parent (ZoomControl component), posée au commit et non pendant le rendu + useImperativeHandle(zoomApiRef, () => ({ + zoomTo: (newScale) => { + const canvas = canvasRef.current + if (!canvas) return + const rect = canvas.getBoundingClientRect() + const cx = rect.width / 2 + const cy = rect.height / 2 + const base = targetTransformRef.current + const clamped = Math.min(MAX_SCALE, Math.max(MIN_SCALE, newScale)) + const ratio = clamped / base.scale + targetTransformRef.current = { + scale: clamped, + x: cx - ratio * (cx - base.x), + y: cy - ratio * (cy - base.y), + } + }, + resetZoom: () => { + if (initialTransformRef.current) { + targetTransformRef.current = { ...initialTransformRef.current } + } + }, + // Recherche : glisse jusqu'à la personne, dans la partie de l'écran que la fiche ne couvre pas + // (fiche à gauche sur ordinateur, 408 px ; par le bas sur téléphone) + focusPerson: (personId) => { + const canvas = canvasRef.current + const node = layoutDataRef.current?.children.find(n => n.id === `p-${personId}`) + if (!canvas || !node) return + const rect = canvas.getBoundingClientRect() + const pos = getAnimatedPos(node, performance.now()) + const scale = Math.min(MAX_SCALE, Math.max(targetTransformRef.current.scale, 1)) + const phone = rect.width <= 768 + const screenX = phone ? rect.width / 2 : (408 + rect.width) / 2 + const screenY = phone ? rect.height * 0.22 : rect.height / 2 + targetTransformRef.current = { scale, x: screenX - pos.cx * scale, y: screenY - pos.cy * scale } + }, + getScale: () => transformRef.current.scale, + MIN_SCALE, + MAX_SCALE, + }), [getAnimatedPos]) // Zoom util (used by pinch) const zoomAt = useCallback((factor, center) => { diff --git a/frontend/src/hooks/useTreeAccess.js b/frontend/src/hooks/useTreeAccess.js index 216a138..28fb03d 100644 --- a/frontend/src/hooks/useTreeAccess.js +++ b/frontend/src/hooks/useTreeAccess.js @@ -52,8 +52,9 @@ export function useTreeAccess() { // Change à chaque chargement des données, même sans nouveau placement : l'arbre y charge les images manquantes const [assetRevision, setAssetRevision] = useState(0) // Horodatage des portraits stable pendant la session : un rechargement ne force pas à tout retélécharger. - // Il ne change que lorsqu'un portrait change (envoi d'une nouvelle photo). - const avatarCacheBustRef = useRef(Date.now()) + // Il ne change que lorsqu'un portrait change (envoi d'une nouvelle photo). Posé au premier chargement + // d'un arbre, pas au rendu (Date.now() rendrait le rendu impur). + const avatarCacheBustRef = useRef(null) const canEditCurrentTree = useMemo(() => { const role = normalizeRole(treeContext.role) @@ -70,7 +71,7 @@ export function useTreeAccess() { loadGraphData(graphPayload.graph, { mediaToken: graphPayload.mediaToken, apiBaseUrl: API_BASE_URL, - avatarCacheBust: options.avatarCacheBust ?? avatarCacheBustRef.current, + avatarCacheBust: options.avatarCacheBust ?? (avatarCacheBustRef.current ??= Date.now()), }) if (options.avatarCacheBust) avatarCacheBustRef.current = options.avatarCacheBust setAssetRevision((current) => current + 1)