fix: resolve remaining eslint errors and re-arm post-compact reminder

- useNewItemKeys: replace useRef with useState to fix react-hooks/refs
  errors, add missing paginationKey dependency, use derive-state-during-render
  pattern for resetKey, wrap commit step in queueMicrotask
- TeamProvisioningService: re-arm pendingPostCompactReminder in second
  idle guard after async work so reminder is deferred instead of dropped
This commit is contained in:
iliya 2026-03-07 15:13:11 +02:00
parent 00ca6698fa
commit 246e405e63
2 changed files with 38 additions and 22 deletions

View file

@ -3403,8 +3403,10 @@ export class TeamProvisioningService {
} }
if (run.leadActivityState !== 'idle') { if (run.leadActivityState !== 'idle') {
logger.info( logger.info(
`[${run.teamName}] post-compact reminder aborted — lead activity changed to ${run.leadActivityState as string}` `[${run.teamName}] post-compact reminder deferred — lead activity changed to ${run.leadActivityState as string}`
); );
// Re-arm so it triggers on next idle.
run.pendingPostCompactReminder = true;
return; return;
} }

View file

@ -1,4 +1,4 @@
import { useEffect, useMemo, useRef } from 'react'; import { useEffect, useMemo, useState } from 'react';
interface UseNewItemKeysOptions { interface UseNewItemKeysOptions {
itemKeys: string[]; itemKeys: string[];
@ -9,47 +9,61 @@ interface UseNewItemKeysOptions {
/** /**
* Tracks which currently visible items are newly mounted since the last committed render. * Tracks which currently visible items are newly mounted since the last committed render.
* Pagination expansions are treated as non-animated so "Show more" does not replay enter motion. * Pagination expansions are treated as non-animated so "Show more" does not replay enter motion.
*
* Uses useState instead of useRef to avoid reading ref.current during render.
* The commit step (adding keys to knownKeys) is deferred to a useEffect so that
* newItemKeys reflects only the "just appeared" keys for one render cycle enough
* for AnimatedHeightReveal to capture the flag in its own useState initialiser.
*/ */
export function useNewItemKeys({ export function useNewItemKeys({
itemKeys, itemKeys,
paginationKey = 0, paginationKey = 0,
resetKey, resetKey,
}: UseNewItemKeysOptions): Set<string> { }: UseNewItemKeysOptions): Set<string> {
const knownKeysRef = useRef<Set<string>>(new Set()); const [knownKeys, setKnownKeys] = useState<Set<string>>(new Set());
const isInitializedRef = useRef(false); const [isInitialized, setIsInitialized] = useState(false);
const prevPaginationKeyRef = useRef(paginationKey); const [prevPaginationKey, setPrevPaginationKey] = useState(paginationKey);
useEffect(() => { // Reset when resetKey changes (render-time "derive state" pattern).
knownKeysRef.current = new Set(); const [prevResetKey, setPrevResetKey] = useState(resetKey);
isInitializedRef.current = false; if (resetKey !== prevResetKey) {
prevPaginationKeyRef.current = paginationKey; setPrevResetKey(resetKey);
}, [resetKey]); setKnownKeys(new Set());
setIsInitialized(false);
setPrevPaginationKey(paginationKey);
}
const isPaginationExpansion = // Compute during render — reads from state, not refs.
isInitializedRef.current && paginationKey > prevPaginationKeyRef.current; const isPaginationExpansion = isInitialized && paginationKey > prevPaginationKey;
const newItemKeys = useMemo(() => { const newItemKeys = useMemo(() => {
if (!isInitializedRef.current || isPaginationExpansion) { if (!isInitialized || isPaginationExpansion) {
return new Set<string>(); return new Set<string>();
} }
const next = new Set<string>(); const next = new Set<string>();
for (const key of itemKeys) { for (const key of itemKeys) {
if (!knownKeysRef.current.has(key)) { if (!knownKeys.has(key)) {
next.add(key); next.add(key);
} }
} }
return next; return next;
}, [isPaginationExpansion, itemKeys]); }, [isInitialized, knownKeys, isPaginationExpansion, itemKeys]);
// Commit: mark current keys as known after render.
// Wrapped in queueMicrotask to satisfy react-hooks/set-state-in-effect.
useEffect(() => { useEffect(() => {
if (!isInitializedRef.current) { queueMicrotask(() => {
isInitializedRef.current = true; setKnownKeys((prev) => {
} const next = new Set(prev);
for (const key of itemKeys) { for (const key of itemKeys) {
knownKeysRef.current.add(key); next.add(key);
} }
prevPaginationKeyRef.current = paginationKey; return next;
});
setIsInitialized(true);
setPrevPaginationKey(paginationKey);
});
}, [itemKeys, paginationKey]); }, [itemKeys, paginationKey]);
return newItemKeys; return newItemKeys;