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') {
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;
}

View file

@ -1,4 +1,4 @@
import { useEffect, useMemo, useRef } from 'react';
import { useEffect, useMemo, useState } from 'react';
interface UseNewItemKeysOptions {
itemKeys: string[];
@ -9,47 +9,61 @@ interface UseNewItemKeysOptions {
/**
* 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.
*
* 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({
itemKeys,
paginationKey = 0,
resetKey,
}: UseNewItemKeysOptions): Set<string> {
const knownKeysRef = useRef<Set<string>>(new Set());
const isInitializedRef = useRef(false);
const prevPaginationKeyRef = useRef(paginationKey);
const [knownKeys, setKnownKeys] = useState<Set<string>>(new Set());
const [isInitialized, setIsInitialized] = useState(false);
const [prevPaginationKey, setPrevPaginationKey] = useState(paginationKey);
useEffect(() => {
knownKeysRef.current = new Set();
isInitializedRef.current = false;
prevPaginationKeyRef.current = paginationKey;
}, [resetKey]);
// Reset when resetKey changes (render-time "derive state" pattern).
const [prevResetKey, setPrevResetKey] = useState(resetKey);
if (resetKey !== prevResetKey) {
setPrevResetKey(resetKey);
setKnownKeys(new Set());
setIsInitialized(false);
setPrevPaginationKey(paginationKey);
}
const isPaginationExpansion =
isInitializedRef.current && paginationKey > prevPaginationKeyRef.current;
// Compute during render — reads from state, not refs.
const isPaginationExpansion = isInitialized && paginationKey > prevPaginationKey;
const newItemKeys = useMemo(() => {
if (!isInitializedRef.current || isPaginationExpansion) {
if (!isInitialized || isPaginationExpansion) {
return new Set<string>();
}
const next = new Set<string>();
for (const key of itemKeys) {
if (!knownKeysRef.current.has(key)) {
if (!knownKeys.has(key)) {
next.add(key);
}
}
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(() => {
if (!isInitializedRef.current) {
isInitializedRef.current = true;
}
for (const key of itemKeys) {
knownKeysRef.current.add(key);
}
prevPaginationKeyRef.current = paginationKey;
queueMicrotask(() => {
setKnownKeys((prev) => {
const next = new Set(prev);
for (const key of itemKeys) {
next.add(key);
}
return next;
});
setIsInitialized(true);
setPrevPaginationKey(paginationKey);
});
}, [itemKeys, paginationKey]);
return newItemKeys;