fix: restore ProductsContext API + add missing query-config exports (unblocks production deploy)#604
fix: restore ProductsContext API + add missing query-config exports (unblocks production deploy)#604adm01-debug wants to merge 4 commits into
Conversation
…tsByIds exports FloatingCompareBar.tsx has been importing `useProductsContextSafe` and calling `ctx?.getProductsByIds(uniqueIds)` since some prior refactor, but neither symbol existed in ProductsContext.tsx — causing the Vite build to fail at module resolution: "useProductsContextSafe" is not exported by "src/contexts/ProductsContext.tsx" imported by "src/components/compare/FloatingCompareBar.tsx" This broke every production deploy from commit d456899 onwards (PR #601 hover crossfade + PR #602 audit fixes + Lovable visual edits), freezing the live site on an older build. Additions: - `getProductsByIds(ids: string[]): Product[]` — batch lookup that filters the cached products array by a Set of ids. Returns [] for empty input. - `useProductsContextSafe()` — non-throwing variant of useProducts(); returns `ProductsContextType | null` so components rendered above the provider (floating bars, portals) can opt-in instead of crashing. `useProducts()` keeps its original throw-on-missing-provider behavior to preserve the strict contract for components inside the provider tree.
Commit 8818bea ("simplify HMR guard") was misleadingly named — it actually REWROTE the entire ProductsContext.tsx, replacing a rich lazy-fetching API with a minimal one and removing 4 exports that 21 consumers depend on: REMOVED in 8818bea: CONSUMERS BROKEN: - export const ProductsContext useSearch.ts, useSellerCartsPage.ts - export function useProductsContext() 15 files - export function useProductsContextSafe() FloatingCompareBar.tsx - API: getProductById, getProductsByIds, all of the above registerProducts, batched-fetch, HMR recovery via setKey Every production deploy since 8818bea has been state=ERROR (build failure at "useProductsContextSafe is not exported"). The live site is frozen on the 253cebc deploy (last READY). PRs #601 (set_image hover) and #602 are queued behind this bug. This commit restores the full ProductsContext.tsx from commit 253cebc verbatim, preserving the rich API: - export const ProductsContext (raw context for direct useContext) - export ProductsProvider (lazy-fetching with 50ms batching) - export useProductsContext() (strict: returns fallback in dev, fallback in prod; never crashes) - export useProductsContextSafe() (safe: returns null outside provider) API: - products: Product[] - isLoading: boolean - getProductById(id): Product | undefined (lazy-fetches if missing) - getProductsByIds(ids): Product[] (lazy-fetches missing in batch) - registerProducts(products): void (caller-side cache hydration) The fetchPromobrindProducts({ filters, limit }) call in this file is backward-compatible with the current main's products.ts (verified: that file was not changed by 8818bea — only the context consumer was rewritten). Restoring this unblocks all 21 ProductsContext consumers and the entire production deploy pipeline.
…ategoriesQuery
useExternalCategoriesQuery.ts imports CACHE_TIMES.STABLE and GC_TIMES.TECNICAS
from @/lib/query-config but neither symbol was ever exported, breaking the
build at module resolution:
src/hooks/products/useExternalCategoriesQuery.ts (8:9):
"CACHE_TIMES" is not exported by "src/lib/query-config.ts"
A grep across the entire repo confirms CACHE_TIMES.STABLE and GC_TIMES.TECNICAS
are referenced only in this one consumer and are not defined anywhere — the
producer side was never written (or was deleted in some prior refactor).
This commit adds the two named-tier objects backed by the existing STALE_*
constants. Values:
CACHE_TIMES.STABLE = STALE_STATIC (30 min) — categories/suppliers
CACHE_TIMES.SEMI = STALE_SEMI (10 min) — product catalog
CACHE_TIMES.LIVE = STALE_LIVE ( 2 min) — quotes/notifications
CACHE_TIMES.REALTIME = STALE_REALTIME (30 s) — connection/health
GC_TIMES.DEFAULT = 15 min (matches existing GC_DEFAULT)
GC_TIMES.LONG = 30 min (new — for slow-changing taxonomies)
GC_TIMES.TECNICAS = 30 min (alias of LONG — used by categories)
The prefix→tier auto-routing below stays as the default for hooks that
don't pass an explicit staleTime; CACHE_TIMES/GC_TIMES are an *opt-in*
escape hatch for hooks that want to override the tier (like the categories
hook, which wants STABLE staleTime + extended gcTime).
Unblocks build after the ProductsContext restore in the parent commit.
The Vercel build was still failing after the CACHE_TIMES/GC_TIMES fix because
several other consumers import symbols from @/lib/query-config that were never
exported:
src/hooks/products/usePrefetchProduct.ts:
QUERY_KEY_PREFIXES, PRODUTOS_QUERY_OPTIONS
src/hooks/tecnicas/useTecnicasList.ts:
TECNICAS_QUERY_OPTIONS
src/hooks/tecnicas/useTabelasPreco.ts:
TABELAS_PRECO_QUERY_OPTIONS
tests/lib/query-config*.test.ts:
getStaleTimeForKey, getGcTimeForKey, STABLE_DATA_QUERY_OPTIONS,
+ all of the above
All of these imports existed in main but the producer side was never (or
no longer) written. The build halted at the FIRST unresolved import each time,
so each round of investigation only surfaced one bug at a time. This commit
adds ALL 7 missing exports in one shot, matching the API contract pinned by
the existing tests/lib/query-config*.test.ts files.
API additions:
CACHE_TIMES — extended with the full ordered tier set the tests require:
NONE (0) < REALTIME (1m) < DYNAMIC (5m) < PRODUTOS (10m)
< TABELAS_PRECO (15m) < TECNICAS (30m) < STABLE (1h) < VERY_STABLE (24h)
GC_TIMES — DEFAULT (15m), TECNICAS (30m), LONG (1h)
QUERY_KEY_PREFIXES — canonical first-element strings for queryKey tuples
(PRODUTOS, PRODUTO_PERSONALIZACAO, CATEGORIES, COLORS, TECNICAS,
TABELAS_PRECO, etc.)
getStaleTimeForKey(queryKey) — prefix→staleTime resolver
getGcTimeForKey(queryKey) — prefix→gcTime resolver
Both fall back to PRODUTOS / DEFAULT for empty/non-array/non-string keys
or unknown prefixes — matches the test expectations exactly.
PRODUTOS_QUERY_OPTIONS — { staleTime: CACHE_TIMES.PRODUTOS, ... }
TECNICAS_QUERY_OPTIONS — { staleTime: CACHE_TIMES.TECNICAS, ... }
TABELAS_PRECO_QUERY_OPTIONS — { staleTime: CACHE_TIMES.TABELAS_PRECO, ... }
STABLE_DATA_QUERY_OPTIONS — { staleTime: CACHE_TIMES.STABLE, ... }
All four have refetchOnWindowFocus=false; TECNICAS also disables
refetchOnMount (matches test).
NOTE on CACHE_TIMES.STABLE value: previous commit set STABLE=30min based on
the old in-file STALE_STATIC constant. Tests now pin STABLE=1h, so the value
is updated to match. useExternalCategoriesQuery (the only non-test consumer
of CACHE_TIMES.STABLE) uses it for category caching where 1h is actually a
better fit than 30min — no behavior regression.
createQueryClient is unchanged in behavior but now also auto-routes gcTime
(not just staleTime) via the same prefix-tier observer.
Should be the final piece unblocking the production deploy pipeline. If any
*other* unrelated import is still broken, it will surface in the next
deploy and can be fixed in a follow-up commit.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughO PR reimplementa o ChangesCache Strategy e ProductsContext Refactor
Sequence DiagramsequenceDiagram
participant Consumer as Consumidor
participant ProductsContext as ProductsProvider
participant CacheMap as Cache (Map)
participant BatchScheduler as scheduleBatchFetch
participant API as Fetch Products
participant QueryClient as QueryClient Observer
Consumer->>ProductsContext: getProductById(123)
ProductsContext->>CacheMap: lookup 123
alt Cache miss
ProductsContext->>BatchScheduler: registra ID 123 na janela 50ms
BatchScheduler-->>ProductsContext: aguarda 50ms
BatchScheduler->>API: fetch batch [123, 456, ...]
API-->>CacheMap: retorna produtos
CacheMap->>ProductsContext: atualiza products via useMemo
ProductsContext-->>Consumer: produto encontrado
else Cache hit
CacheMap-->>ProductsContext: retorna produto do cache
ProductsContext-->>Consumer: produto imediato
end
Note over QueryClient: Para cada queryKey, observer aplica<br/>getStaleTimeForKey + getGcTimeForKey
QueryClient->>QueryClient: preenche staleTime/gcTime por prefixo
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
There was a problem hiding this comment.
Pull request overview
Restores the previously-expected ProductsContext public API and adds missing query-config exports so downstream imports resolve again and production deploys can proceed.
Changes:
- Reintroduces
ProductsContextexports (ProductsContext,ProductsProvider,useProductsContext,useProductsContextSafe) and restores the richer cached/batched product lookup API. - Adds missing cache-time constants, query-key prefix constants, per-domain query option presets, and per-key stale/gc time routing helpers to
src/lib/query-config.ts. - Updates
createQueryClient()to attempt per-queryKey routing forstaleTimeandgcTimeviaqueryCachesubscription.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/lib/query-config.ts | Re-exports missing cache/query config symbols and adds stale/gc routing + presets. |
| src/contexts/ProductsContext.tsx | Restores the richer ProductsContext API with lazy/batched fetching and safe/strict hooks. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| client.getQueryCache().subscribe((event) => { | ||
| if (event.type === 'added' || event.type === 'updated') { | ||
| const query = event.query; | ||
| if (query.options.staleTime === undefined) { | ||
| query.options.staleTime = resolveStaleTime(query.queryKey); | ||
| query.options.staleTime = getStaleTimeForKey(query.queryKey); | ||
| } | ||
| if (query.options.gcTime === undefined) { | ||
| query.options.gcTime = getGcTimeForKey(query.queryKey); | ||
| } | ||
| } | ||
| }); |
| /** Resolves a single id (returns undefined if not cached; does NOT trigger fetch) */ | ||
| getProductById: (id: string) => Product | undefined; |
adm01-debug
left a comment
There was a problem hiding this comment.
🔄 PR obsoleto — fechado pelo merge do PR #605
O PR #605 (branch fix/restore-productscontext-query-exports) foi mergeado em main às 18:09:16 UTC (commit 8b86b4d04c0bc4b68af665e5cf2e2af835bd3386) com a mesma diagnose e fix:
- Restaura
ProductsContext.tsxdo commit253cebc(idêntico ao que este PR faz) - Adiciona 6 exports faltantes em
query-config.ts(este PR adicionou 9 — superset compatível)
Resultado: deploy dpl_7rTaCFE65RxyJBgDUCGBmzbS9ony (target=production) → state=READY ✅
Validação HTTP em https://www.promogifts.com.br:
- HTTP 200
- Novo bundle:
/assets/index-BH9F3Xyd.js(199KB) - CSP correto, todas integrações funcionando
Convergência independente: duas sessões de Claude (esta + outra via Claude Code VPS) chegaram exatamente à mesma raiz e à mesma cura. Eu commitei o diagnóstico de Bug A (ProductsContext) primeiro às 17:50 UTC; o PR #605 foi aberto ~10min depois com diagnóstico equivalente.
Este PR pode ser fechado sem merge — está obsoleto.
A branch fix/products-context-add-missing-exports pode ser deletada com segurança.
Pequena diferença a observar: este PR adicionou também STABLE_DATA_QUERY_OPTIONS, getStaleTimeForKey, getGcTimeForKey que são importados apenas por tests/lib/query-config*.test.ts. Se a CI estiver rodando esses testes em algum momento (não rodam no build do Vite), o PR #605 vai falhar ali. Se isso acontecer, basta abrir um follow-up adicionando esses 3 símbolos a src/lib/query-config.ts.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b8c186006f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| idsToFetch.forEach((id) => fetchingRef.current.delete(id)); | ||
| if (mountedRef.current) setIsLoading(false); |
There was a problem hiding this comment.
Track missing IDs to stop repeated fetches
When a saved favorite/compare/recently-viewed item references a product that is inactive, deleted, or otherwise not returned by fetchPromobrindProducts, the fetch completes with that id still absent from cache. This finally block then removes the id from fetchingRef and toggles isLoading, causing consumers that call getProductsByIds() during render to queue the same missing id again on the next render, creating an endless 50ms refetch loop against the products endpoint. The previous implementation marked requested ids as fetched even if no row came back; this path needs an equivalent negative-cache/attempted-id guard.
Useful? React with 👍 / 👎.
| if (query.options.gcTime === undefined) { | ||
| query.options.gcTime = getGcTimeForKey(query.queryKey); | ||
| } |
There was a problem hiding this comment.
Apply per-key gcTime before defaulting
For normal queries that do not pass gcTime, query.options.gcTime is already populated from defaultQueryOptions.queries.gcTime, so this condition is never true and the new PREFIX_GC_MAP is effectively ignored. Queries like ['colors'], ['categories'], or ['suppliers'] that rely on the client-level routing still get the 15-minute default instead of the intended longer GC time; move the fallback/defaulting into getGcTimeForKey or avoid setting a default before this check.
Useful? React with 👍 / 👎.
| return found; | ||
| }, | ||
| [queueFetch], |
There was a problem hiding this comment.
Refresh lookup callbacks when cache changes
For a valid but cold product id, the first getProductsByIds() call returns [] and queues the lazy fetch, but this callback never changes identity when cache is populated because it only depends on queueFetch. Several consumers memoize results with deps like [getProductsByIds, items] (checked RecentlyViewedBar, RecentlyViewedPopover, and the trash views), so they keep rendering the empty result after the fetch succeeds until some unrelated item prop changes. Include a cache/products signal in the callback/deps or update those consumers so the lazy load can actually appear.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/contexts/ProductsContext.tsx`:
- Around line 62-65: isLoading can be set to false prematurely because each
batch sets it true at start and false in finally; change the logic to derive
isLoading from the actual active fetch set/counter instead of toggling it
per-batch: use fetchingRef (or a numeric activeFetchCount) to add batch ids
before starting requests and remove them in finally, then set isLoading =
fetchingRef.current.size > 0 (or activeFetchCount > 0) rather than blindly
setting false in each finally; ensure removals target only that batch's ids,
update any places that currently toggle isLoading (references: isLoading,
fetchingRef, batchIdsRef, batchTimerRef, mountedRef) and keep mountedRef checks
before state updates.
- Around line 169-183: registerProducts currently only adds products when the id
is missing, so updated or richer versions never replace the cached item; change
the logic inside setCache (in registerProducts) to also replace an existing
entry when the incoming product differs from the cached one (e.g., compare
next.get(p.id) !== p or a more specific field like updatedAt/version) — update
next.set(p.id, p) and set changed = true whenever the new product is different
from the stored product so the cache rehydrates with newer data.
- Around line 27-34: getProductById and getProductsByIds currently cause
side-effects (they call queueFetch) which violates their contract and React
render purity; change them to only read from the cache and never call queueFetch
during lookup so they match the documented behavior. Move all
queueFetch/scheduleBatchFetch invocation to event handlers or useEffect hooks
where side-effects are allowed (e.g., expose a separate ensureProducts(ids) or
fetchMissingProducts(ids) method consumers call outside render). Make isLoading
track concurrent batches (use a counter or Set of batch ids instead of a single
boolean) so one batch’s finally doesn’t clear loading while others are active.
Update registerProducts to always upsert/overwrite existing entries instead of
ignoring when next.has(p.id) so cache updates replace stale values. Ensure
references to getProductById, getProductsByIds, queueFetch, scheduleBatchFetch,
isLoading, and registerProducts are updated accordingly.
In `@src/lib/query-config.ts`:
- Around line 71-72: QUERY_KEY_PREFIXES.PRICE_TABLES ('price-tables') is routed
in PREFIX_STALE_MAP but missing from PREFIX_GC_MAP causing it to fall back to
GC_TIMES.DEFAULT and create inconsistent GC policy versus TABELAS_PRECO; add an
entry for QUERY_KEY_PREFIXES.PRICE_TABLES in PREFIX_GC_MAP with the same GC time
as QUERY_KEY_PREFIXES.TABELAS_PRECO (or the intended GC constant) so both
prefixes share the same GC behavior, ensuring the change is applied alongside
the existing mapping for PREFIX_STALE_MAP and any related entries referenced
near PREFIX_GC_MAP and GC_TIMES.
- Around line 193-197: defaultQueryOptions currently sets queries.gcTime =
GC_TIMES.DEFAULT which causes query.options.gcTime to be populated before the
subscriber runs, preventing getGcTimeForKey / PREFIX_GC_MAP logic from ever
executing; remove or unset the default gcTime in defaultQueryOptions (i.e.,
don't prefill queries.gcTime) so the subscriber in createQueryClient can call
getGcTimeForKey per-key and apply PREFIX_GC_MAP tiered GC logic; ensure any code
referencing query.options.gcTime handles undefined and falls back to
getGcTimeForKey when needed.
- Around line 220-228: Don't mutate event.query.options inside
client.getQueryCache().subscribe; instead compute defaults using
getStaleTimeForKey and getGcTimeForKey and call the public API
queryClient.setQueryDefaults(query.queryKey, { staleTime, gcTime }) (or similar
on your `client` instance) so defaults are applied via the QueryClient flow;
remove the in-place assignments to query.options and replace them with
setQueryDefaults calls when event.type is 'added' or 'updated' so the library's
public mechanism manages defaults.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: cfc4eca1-557a-41e9-9967-97f2f2b4f63e
📒 Files selected for processing (2)
src/contexts/ProductsContext.tsxsrc/lib/query-config.ts
| /** Resolves a single id (returns undefined if not cached; does NOT trigger fetch) */ | ||
| getProductById: (id: string) => Product | undefined; | ||
| /** | ||
| * Batch lookup — returns cached products matching the given ids (in any order). | ||
| * Missing ids are silently skipped; queueing a fetch for them is the caller's | ||
| * responsibility (or rely on the lazy queueFetch fallback). | ||
| */ | ||
| getProductsByIds: (ids: string[]) => Product[]; |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In React 18, are scheduling timers or starting network requests during render considered side effects, and can concurrent rendering or StrictMode restart/discard renders after those side effects already ran?
💡 Result:
Yes—side effects like starting timers or network requests should be treated as “side effects” and must not run during render in React 18. React’s docs explicitly require that components and hooks be pure during render and that “side effects should not run in render” because React may render components multiple times for the best user experience [1][2]. Can concurrent rendering or StrictMode restart/discard renders after those side effects already ran? - Concurrent rendering: React can pause rendering, continue later, and even abandon an in-progress render. React guarantees UI consistency by delaying DOM mutations until commit, which implies that anything you did during render (like scheduling a timer or firing a request) would not be part of the “guaranteed” commit behavior, and you must not do it anyway [3]. - StrictMode (development): React intentionally double-invokes certain component functions in development to help you find impure code; StrictMode “calls some of your functions … twice in development” including the component function body (top-level render logic) [4]. React also double-invokes effects in development, and the React team notes that an effect firing twice in StrictMode development can lead to issues like double fetching, but it’s intended to surface bugs; they recommend making effects safe via cleanup/ignores (double fetch has no harm if you handle it correctly) [5][6]. Practical conclusion: - Do not start timers or initiate network requests in render (including top-level component body). Put them in event handlers or in Effects (useEffect/useLayoutEffect) where side effects are allowed [1]. - If you fetch in an effect, assume development/StrictMode may run it more than once; write the effect to be resilient (e.g., abort/cancel, or ignore stale responses, and clean up) [6][5].
Citations:
- 1: https://react.dev/reference/rules/components-and-hooks-must-be-pure
- 2: https://react.dev/learn/render-and-commit
- 3: https://legacy.reactjs.org/blog/2022/03/29/react-v18.html
- 4: https://react.dev/reference/react/StrictMode
- 5: Bug: useEffect called twice in Strict Mode facebook/react#24553
- 6: Bug: useEffect runs twice on component mount (StrictMode, NODE_ENV=development) facebook/react#24502
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/contexts/ProductsContext.tsx"
if [ ! -f "$FILE" ]; then
echo "Missing file: $FILE"
exit 1
fi
echo "=== file: $FILE (head) ==="
sed -n '1,120p' "$FILE" | nl -ba
echo "=== file: $FILE (getters + batch scheduling) ==="
sed -n '120,240p' "$FILE" | nl -ba
echo "=== file: $FILE (tail) ==="
wc -l "$FILE"
tail -n 80 "$FILE" | nl -baRepository: adm01-debug/promo-gifts-v4
Length of output: 186
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/contexts/ProductsContext.tsx"
if [ ! -f "$FILE" ]; then
echo "Missing file: $FILE"
exit 1
fi
echo "=== lines 1-220 (with numbers) ==="
awk '{printf "%d\t%s\n", NR, $0}' "$FILE" | sed -n '1,220p'
echo "=== total lines ==="
wc -l "$FILE"Repository: adm01-debug/promo-gifts-v4
Length of output: 8036
Evitar side effects em getters e corrigir estado/contrato do cache em ProductsContext
getProductById(135-142) egetProductsByIds(146-165) chamamqueueFetchem cache miss, que agendasetTimeout+ request emscheduleBatchFetch(84-118). Se consumidores chamarem isso durante render, quebra a exigência de render “puro” no React 18/StrictMode e pode disparar fetch “fantasma” em reinícios/discards.- Contrato desalinhado: o type diz “does NOT trigger fetch” para
getProductById(27-28), mas a implementação dispara fetch viaqueueFetch. isLoading(49, atualizado emfinallydescheduleBatchFetch111-116) é um boolean único: batches podem sobrepor e umfinallypode setarfalseenquanto outro fetch ainda está em andamento.registerProducts(169-183) só insere se o id não existe (!next.has(p.id)), então ignora updates para IDs já presentes (cache pode ficar congelado no primeiro valor).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/contexts/ProductsContext.tsx` around lines 27 - 34, getProductById and
getProductsByIds currently cause side-effects (they call queueFetch) which
violates their contract and React render purity; change them to only read from
the cache and never call queueFetch during lookup so they match the documented
behavior. Move all queueFetch/scheduleBatchFetch invocation to event handlers or
useEffect hooks where side-effects are allowed (e.g., expose a separate
ensureProducts(ids) or fetchMissingProducts(ids) method consumers call outside
render). Make isLoading track concurrent batches (use a counter or Set of batch
ids instead of a single boolean) so one batch’s finally doesn’t clear loading
while others are active. Update registerProducts to always upsert/overwrite
existing entries instead of ignoring when next.has(p.id) so cache updates
replace stale values. Ensure references to getProductById, getProductsByIds,
queueFetch, scheduleBatchFetch, isLoading, and registerProducts are updated
accordingly.
| const fetchingRef = useRef<Set<string>>(new Set()); | ||
| const batchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); | ||
| const batchIdsRef = useRef<Set<string>>(new Set()); | ||
| const mountedRef = useRef(true); |
There was a problem hiding this comment.
isLoading pode ir para false com outro batch ainda em voo.
Cada batch seta true no início e false no finally. Se um segundo lote começar antes de o primeiro terminar, o finally do primeiro derruba o flag mesmo com outra busca ativa, então o contexto publica um estado de loading incorreto.
💡 Sugestão de ajuste
const cacheRef = useRef<Map<string, Product>>(cache);
const fetchingRef = useRef<Set<string>>(new Set());
+ const inFlightBatchesRef = useRef(0);
const batchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const batchIdsRef = useRef<Set<string>>(new Set());
const mountedRef = useRef(true);
@@
idsToFetch.forEach((id) => fetchingRef.current.add(id));
+ inFlightBatchesRef.current += 1;
if (mountedRef.current) setIsLoading(true);
@@
} finally {
idsToFetch.forEach((id) => fetchingRef.current.delete(id));
- if (mountedRef.current) setIsLoading(false);
+ inFlightBatchesRef.current -= 1;
+ if (mountedRef.current) {
+ setIsLoading(inFlightBatchesRef.current > 0);
+ }
}Also applies to: 94-115
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/contexts/ProductsContext.tsx` around lines 62 - 65, isLoading can be set
to false prematurely because each batch sets it true at start and false in
finally; change the logic to derive isLoading from the actual active fetch
set/counter instead of toggling it per-batch: use fetchingRef (or a numeric
activeFetchCount) to add batch ids before starting requests and remove them in
finally, then set isLoading = fetchingRef.current.size > 0 (or activeFetchCount
> 0) rather than blindly setting false in each finally; ensure removals target
only that batch's ids, update any places that currently toggle isLoading
(references: isLoading, fetchingRef, batchIdsRef, batchTimerRef, mountedRef) and
keep mountedRef checks before state updates.
| // Register products from external sources (e.g. page-level useProducts queries) | ||
| const registerProducts = useCallback((products: Product[]) => { | ||
| if (products.length === 0) return; | ||
| setCache((prev) => { | ||
| const next = new Map(prev); | ||
| let changed = false; | ||
| for (const p of products) { | ||
| if (!next.has(p.id)) { | ||
| next.set(p.id, p); | ||
| changed = true; | ||
| } | ||
| } | ||
| return changed ? next : prev; | ||
| }); | ||
| }, []); |
There was a problem hiding this comment.
registerProducts ignora refresh de IDs já cacheados.
Hoje ele só insere quando !next.has(p.id). Se uma query de página trouxer uma versão mais nova ou mais completa de um produto já cacheado, o contexto continua servindo o valor antigo e nunca reidrata esse item.
💡 Sugestão de ajuste
const registerProducts = useCallback((products: Product[]) => {
if (products.length === 0) return;
setCache((prev) => {
const next = new Map(prev);
let changed = false;
for (const p of products) {
- if (!next.has(p.id)) {
+ if (next.get(p.id) !== p) {
next.set(p.id, p);
changed = true;
}
}
return changed ? next : prev;
});
}, []);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/contexts/ProductsContext.tsx` around lines 169 - 183, registerProducts
currently only adds products when the id is missing, so updated or richer
versions never replace the cached item; change the logic inside setCache (in
registerProducts) to also replace an existing entry when the incoming product
differs from the cached one (e.g., compare next.get(p.id) !== p or a more
specific field like updatedAt/version) — update next.set(p.id, p) and set
changed = true whenever the new product is different from the stored product so
the cache rehydrates with newer data.
| TABELAS_PRECO: 'tabelas-preco', | ||
| PRICE_TABLES: 'price-tables', |
There was a problem hiding this comment.
price-tables ficou sem regra de gcTime.
Você exportou QUERY_KEY_PREFIXES.PRICE_TABLES e já roteia esse prefixo em PREFIX_STALE_MAP, mas PREFIX_GC_MAP não cobre 'price-tables'. Essas queries vão cair no GC_TIMES.DEFAULT, diferente de tabelas-preco, criando política inconsistente para o mesmo domínio.
Also applies to: 96-96, 123-131
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/query-config.ts` around lines 71 - 72,
QUERY_KEY_PREFIXES.PRICE_TABLES ('price-tables') is routed in PREFIX_STALE_MAP
but missing from PREFIX_GC_MAP causing it to fall back to GC_TIMES.DEFAULT and
create inconsistent GC policy versus TABELAS_PRECO; add an entry for
QUERY_KEY_PREFIXES.PRICE_TABLES in PREFIX_GC_MAP with the same GC time as
QUERY_KEY_PREFIXES.TABELAS_PRECO (or the intended GC constant) so both prefixes
share the same GC behavior, ensuring the change is applied alongside the
existing mapping for PREFIX_STALE_MAP and any related entries referenced near
PREFIX_GC_MAP and GC_TIMES.
| export const defaultQueryOptions: DefaultOptions = { | ||
| queries: { | ||
| // staleTime is resolved per-key at runtime (see createQueryClient) | ||
| gcTime: GC_DEFAULT, | ||
| // staleTime / gcTime are resolved per-key at runtime (see createQueryClient) | ||
| gcTime: GC_TIMES.DEFAULT, | ||
| retry: (failureCount, error) => { |
There was a problem hiding this comment.
O roteamento de gcTime por prefixo não entra nos casos normais.
Como defaultQueryOptions.queries.gcTime já define GC_TIMES.DEFAULT, query.options.gcTime chega preenchido no subscriber e o bloco das Lines 226-227 não roda para queries comuns. Na prática, getGcTimeForKey/PREFIX_GC_MAP ficam quase mortos e este PR não aplica o GC tiered que ele promete.
Also applies to: 226-227
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/query-config.ts` around lines 193 - 197, defaultQueryOptions
currently sets queries.gcTime = GC_TIMES.DEFAULT which causes
query.options.gcTime to be populated before the subscriber runs, preventing
getGcTimeForKey / PREFIX_GC_MAP logic from ever executing; remove or unset the
default gcTime in defaultQueryOptions (i.e., don't prefill queries.gcTime) so
the subscriber in createQueryClient can call getGcTimeForKey per-key and apply
PREFIX_GC_MAP tiered GC logic; ensure any code referencing query.options.gcTime
handles undefined and falls back to getGcTimeForKey when needed.
| client.getQueryCache().subscribe((event) => { | ||
| if (event.type === 'added' || event.type === 'updated') { | ||
| const query = event.query; | ||
| if (query.options.staleTime === undefined) { | ||
| query.options.staleTime = resolveStaleTime(query.queryKey); | ||
| query.options.staleTime = getStaleTimeForKey(query.queryKey); | ||
| } | ||
| if (query.options.gcTime === undefined) { | ||
| query.options.gcTime = getGcTimeForKey(query.queryKey); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Show surrounding code
sed -n '180,270p' src/lib/query-config.ts | nl -ba | sed -n '1,120p'
# 2) Show file header (imports, QueryClient creation, etc.)
head -n 80 src/lib/query-config.ts | nl -ba
# 3) Check TanStack Query version
ls package.json pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/null || true
if [ -f package.json ]; then
cat package.json | sed -n '1,160p'
fi
# 4) Find other usages of setQueryDefaults / setOptions
rg -n "setQueryDefaults|setOptions\(|queryClient\.|QueryCache\." src/lib/query-config.ts src -S || true
# 5) Locate where getStaleTimeForKey / getGcTimeForKey are defined
rg -n "function getStaleTimeForKey|getStaleTimeForKey|getGcTimeForKey|getGcTimeForKey" src/lib/query-config.ts src -S || trueRepository: adm01-debug/promo-gifts-v4
Length of output: 113
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Show surrounding code (with line numbers via awk)
awk 'NR>=180 && NR<=270 {printf "%5d\t%s\n", NR, $0}' src/lib/query-config.ts
# 2) Show file header
awk 'NR<=120 {printf "%5d\t%s\n", NR, $0}' src/lib/query-config.ts
# 3) Show TanStack Query version from package.json
if [ -f package.json ]; then
cat package.json | sed -n '1,200p' | rg -n "tanstack|query|`@tanstack`|react-query" || true
cat package.json | rg -n "\"`@tanstack/react-query`\"|\"`@tanstack/query-core`\"" || true
fi
# 4) Search for setQueryDefaults/setOptions usage
rg -n "setQueryDefaults|setOptions\(|QueryCache\." src -S || true
# 5) Locate definitions of getStaleTimeForKey / getGcTimeForKey
rg -n "getStaleTimeForKey|getGcTimeForKey" src/lib/query-config.ts -S || trueRepository: adm01-debug/promo-gifts-v4
Length of output: 8159
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Confirm whether setQueryDefaults / setOptions exist anywhere in src
rg -n "setQueryDefaults\(|setOptions\(" src -S || true
# Look for tests mentioned in comments / likely naming
find . -maxdepth 4 -type f \( -name "*query-config*test*" -o -name "*query-config*.test.*" -o -name "*query-config*.spec.*" \) 2>/dev/null || true
# Search tests for getStaleTimeForKey / getGcTimeForKey
rg -n "getStaleTimeForKey|getGcTimeForKey|CACHE_TIMES|GC_TIMES" . -S --glob '*test*' --glob '*spec*' 2>/dev/null || trueRepository: adm01-debug/promo-gifts-v4
Length of output: 4798
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Search whole repo for setQueryDefaults / setOptions
rg -n "setQueryDefaults\(|setOptions\(" . -S --glob '*.{ts,tsx,js,jsx}' || true
# 2) Search whole repo for QueryCache subscribe usages
rg -n "getQueryCache\(\)\.subscribe|QueryCache.*subscribe" . -S --glob '*.{ts,tsx,js,jsx}' || true
# 3) Inspect createQueryClient behavior tests (if any mention subscribe/mutation)
sed -n '1,220p' tests/lib/query-config.test.ts | cat
echo "-----"
sed -n '1,220p' tests/lib/query-config-extended.test.ts | catRepository: adm01-debug/promo-gifts-v4
Length of output: 6111
🌐 Web query:
TanStack Query v5 QueryCache subscribe event.query.options mutate setOptions gcTime staleTime setQueryDefaults
💡 Result:
In TanStack Query v5, the items in your query map to these APIs/behaviors: 1) QueryCache.subscribe(event) Use queryClient.getQueryCache.subscribe(callback) to subscribe to query-cache updates as a whole. The callback receives a QueryCacheNotifyEvent and is called for “safe/known updates” such as query state changes, queries being updated, added, or removed [1]. Example (v5): const queryCache = queryClient.getQueryCache const unsubscribe = queryCache.subscribe((event) => { console.log(event.type, event.query) }) This subscribe method’s callback signature is callback: (event: QueryCacheNotifyEvent) => void and it returns an unsubscribe function [1]. 2) “event.query.options” The QueryCacheNotifyEvent includes a query object (event.query). In the official docs example, the event payload includes event.query (and you can log it), but the reference docs snippet doesn’t explicitly document the nested property name “options” on that query object [1]. So: the supported/officially documented part here is that event.query is present; accessing event.query.options is not directly confirmed by the cited docs pages we found. 3) Query.setOptions / mutate setOptions TanStack Query v5 does have query instance methods like query.setState (mentioned as a tracked update mechanism for firing queryCache.subscribe callbacks) [1], but the searched authoritative docs did not surface a specific “setOptions” method name or a “mutate setOptions” phrasing on QueryCache. What is confirmed is that cache subscriptions fire when updates happen via tracked mechanisms such as query.setState and queryClient.removeQueries [1]. 4) gcTime and staleTime - staleTime: controls how long cached data is considered fresh before it becomes stale. Default is staleTime: 0 (immediately stale) [2]. While data is fresh, it is read from cache without triggering refetches; after it becomes stale, it may be refetched based on other conditions [2]. - gcTime: controls how long inactive queries remain in memory before being garbage collected; default is 5 minutes. The v5 migration rename is cacheTime -> gcTime [3]. The caching guide explicitly states gcTime determines how long inactive queries remain in memory before being garbage collected and that the default is 5 minutes [4]. 5) setQueryDefaults To set default query options for matching query keys, use queryClient.setQueryDefaults(queryKey, options). Official docs show: queryClient.setQueryDefaults(['posts'], { queryFn: fetchPosts }) [5]. The QueryClient reference also documents getQueryDefaults/setQueryDefaults and notes that if multiple query defaults match a query key, they are merged in registration order (more generic to more specific so specific overrides generic) [5]. How to combine these in practice (common pattern) - Configure defaults once (staleTime/gcTime/queryFn) with setQueryDefaults. - Subscribe to query-cache updates with queryCache.subscribe to react to query changes globally. Authoritative references used above: - QueryCache.subscribe behavior and callback shape [1] - QueryClient.setQueryDefaults and merging/registration order [5] - staleTime defaults/meaning and gcTime default/meaning [2][4] - v5 migration cacheTime -> gcTime rename [3]
Citations:
- 1: https://tanstack.com/query/latest/docs/reference/QueryCache
- 2: https://tanstack.com/query/v5/docs/framework/react/guides/important-defaults
- 3: https://tanstack-query.mintlify.app/migration/migrating-to-v5
- 4: https://tanstack-query.mintlify.app/guides/caching
- 5: https://tanstack.com/query/latest/docs/reference/QueryClient
Evite mutar query.options diretamente no client.getQueryCache().subscribe (src/lib/query-config.ts, ~linhas 220-228).
Essa abordagem depende de um detalhe não documentado da payload do QueryCache.subscribe (você acessa event.query.options e altera staleTime/gcTime via mutation). Preferir configurar defaults pelas chaves via API pública queryClient.setQueryDefaults(queryKey, options) (ex.: usando prefixos do queryKey) e deixar o fluxo interno aplicar staleTime/gcTime, reduzindo risco de desincronização/regressões. tanstack.com tanstack.com
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/query-config.ts` around lines 220 - 228, Don't mutate
event.query.options inside client.getQueryCache().subscribe; instead compute
defaults using getStaleTimeForKey and getGcTimeForKey and call the public API
queryClient.setQueryDefaults(query.queryKey, { staleTime, gcTime }) (or similar
on your `client` instance) so defaults are applied via the QueryClient flow;
remove the in-place assignments to query.options and replace them with
setQueryDefaults calls when event.type is 'added' or 'updated' so the library's
public mechanism manages defaults.
There was a problem hiding this comment.
5 issues found across 2 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/lib/query-config.ts">
<violation number="1" location="src/lib/query-config.ts:226">
P2: Per-key `gcTime` routing is effectively skipped because `gcTime` is defaulted before the `undefined` check, so mapped prefixes keep the default retention.</violation>
</file>
<file name="src/contexts/ProductsContext.tsx">
<violation number="1" location="src/contexts/ProductsContext.tsx:27">
P3: `getProductById` documentation is incorrect: it says no fetch is triggered, but the function queues a fetch on cache miss.</violation>
<violation number="2" location="src/contexts/ProductsContext.tsx:114">
P1: Missing negative-cache guard causes infinite refetch loop for non-existent product IDs. When a requested ID isn't returned by `fetchPromobrindProducts` (e.g. deleted/inactive product), the `finally` block removes it from `fetchingRef` but it never enters `cache`. On the next render, `getProductsByIds`/`getProductById` will see the ID is absent from `cache`, `fetchingRef`, and `batchIdsRef`, and will re-queue it — repeating every 50 ms indefinitely. Track requested-but-not-returned IDs (e.g. in a `notFoundRef` set) and include them in the `queueFetch` filter to break the cycle.</violation>
<violation number="3" location="src/contexts/ProductsContext.tsx:115">
P2: `isLoading` can become false while requests are still in flight when batched fetches overlap.</violation>
<violation number="4" location="src/contexts/ProductsContext.tsx:143">
P2: `getProductsByIds` reads from `cacheRef.current` (a ref) but its identity never changes when `cache` state is updated because it only depends on `[queueFetch]`. Consumers that memoize results via `useMemo(() => getProductsByIds(ids), [getProductsByIds, ids])` will keep the stale empty array returned before the lazy fetch completed. Include a cache-derived signal (e.g. `cache` or `cache.size`) in the dependency array so the callback identity changes when new products arrive, or return the lookup directly from state rather than the ref.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| } catch (err) { | ||
| logger.warn('[ProductsContext] Failed to fetch products by IDs:', err); | ||
| } finally { | ||
| idsToFetch.forEach((id) => fetchingRef.current.delete(id)); |
There was a problem hiding this comment.
P1: Missing negative-cache guard causes infinite refetch loop for non-existent product IDs. When a requested ID isn't returned by fetchPromobrindProducts (e.g. deleted/inactive product), the finally block removes it from fetchingRef but it never enters cache. On the next render, getProductsByIds/getProductById will see the ID is absent from cache, fetchingRef, and batchIdsRef, and will re-queue it — repeating every 50 ms indefinitely. Track requested-but-not-returned IDs (e.g. in a notFoundRef set) and include them in the queueFetch filter to break the cycle.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/contexts/ProductsContext.tsx, line 114:
<comment>Missing negative-cache guard causes infinite refetch loop for non-existent product IDs. When a requested ID isn't returned by `fetchPromobrindProducts` (e.g. deleted/inactive product), the `finally` block removes it from `fetchingRef` but it never enters `cache`. On the next render, `getProductsByIds`/`getProductById` will see the ID is absent from `cache`, `fetchingRef`, and `batchIdsRef`, and will re-queue it — repeating every 50 ms indefinitely. Track requested-but-not-returned IDs (e.g. in a `notFoundRef` set) and include them in the `queueFetch` filter to break the cycle.</comment>
<file context>
@@ -24,90 +24,222 @@ interface ProductsContextType {
+ } catch (err) {
+ logger.warn('[ProductsContext] Failed to fetch products by IDs:', err);
+ } finally {
+ idsToFetch.forEach((id) => fetchingRef.current.delete(id));
+ if (mountedRef.current) setIsLoading(false);
+ }
</file context>
| query.options.staleTime = resolveStaleTime(query.queryKey); | ||
| query.options.staleTime = getStaleTimeForKey(query.queryKey); | ||
| } | ||
| if (query.options.gcTime === undefined) { |
There was a problem hiding this comment.
P2: Per-key gcTime routing is effectively skipped because gcTime is defaulted before the undefined check, so mapped prefixes keep the default retention.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/lib/query-config.ts, line 226:
<comment>Per-key `gcTime` routing is effectively skipped because `gcTime` is defaulted before the `undefined` check, so mapped prefixes keep the default retention.</comment>
<file context>
@@ -92,29 +208,28 @@ export const defaultQueryOptions: DefaultOptions = {
- query.options.staleTime = resolveStaleTime(query.queryKey);
+ query.options.staleTime = getStaleTimeForKey(query.queryKey);
+ }
+ if (query.options.gcTime === undefined) {
+ query.options.gcTime = getGcTimeForKey(query.queryKey);
}
</file context>
| logger.warn('[ProductsContext] Failed to fetch products by IDs:', err); | ||
| } finally { | ||
| idsToFetch.forEach((id) => fetchingRef.current.delete(id)); | ||
| if (mountedRef.current) setIsLoading(false); |
There was a problem hiding this comment.
P2: isLoading can become false while requests are still in flight when batched fetches overlap.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/contexts/ProductsContext.tsx, line 115:
<comment>`isLoading` can become false while requests are still in flight when batched fetches overlap.</comment>
<file context>
@@ -24,90 +24,222 @@ interface ProductsContextType {
+ logger.warn('[ProductsContext] Failed to fetch products by IDs:', err);
+ } finally {
+ idsToFetch.forEach((id) => fetchingRef.current.delete(id));
+ if (mountedRef.current) setIsLoading(false);
+ }
+ }, 50); // 50ms batching window
</file context>
| } | ||
| return cached; | ||
| }, | ||
| [queueFetch], |
There was a problem hiding this comment.
P2: getProductsByIds reads from cacheRef.current (a ref) but its identity never changes when cache state is updated because it only depends on [queueFetch]. Consumers that memoize results via useMemo(() => getProductsByIds(ids), [getProductsByIds, ids]) will keep the stale empty array returned before the lazy fetch completed. Include a cache-derived signal (e.g. cache or cache.size) in the dependency array so the callback identity changes when new products arrive, or return the lookup directly from state rather than the ref.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/contexts/ProductsContext.tsx, line 143:
<comment>`getProductsByIds` reads from `cacheRef.current` (a ref) but its identity never changes when `cache` state is updated because it only depends on `[queueFetch]`. Consumers that memoize results via `useMemo(() => getProductsByIds(ids), [getProductsByIds, ids])` will keep the stale empty array returned before the lazy fetch completed. Include a cache-derived signal (e.g. `cache` or `cache.size`) in the dependency array so the callback identity changes when new products arrive, or return the lookup directly from state rather than the ref.</comment>
<file context>
@@ -24,90 +24,222 @@ interface ProductsContextType {
+ }
+ return cached;
+ },
+ [queueFetch],
+ );
+
</file context>
| [queueFetch], | |
| [queueFetch, cache], |
| fetchProducts: (ids: string[]) => Promise<void>; | ||
| getProduct: (id: string) => Product | undefined; | ||
| invalidateCache: () => void; | ||
| /** Resolves a single id (returns undefined if not cached; does NOT trigger fetch) */ |
There was a problem hiding this comment.
P3: getProductById documentation is incorrect: it says no fetch is triggered, but the function queues a fetch on cache miss.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/contexts/ProductsContext.tsx, line 27:
<comment>`getProductById` documentation is incorrect: it says no fetch is triggered, but the function queues a fetch on cache miss.</comment>
<file context>
@@ -24,90 +24,222 @@ interface ProductsContextType {
- fetchProducts: (ids: string[]) => Promise<void>;
- getProduct: (id: string) => Product | undefined;
- invalidateCache: () => void;
+ /** Resolves a single id (returns undefined if not cached; does NOT trigger fetch) */
+ getProductById: (id: string) => Product | undefined;
+ /**
</file context>
| /** Resolves a single id (returns undefined if not cached; does NOT trigger fetch) */ | |
| /** Resolves a single id (returns cached value when available; queues lazy fetch on miss) */ |
🚨 Por que esse PR existe
Toda deploy de produção desde 16:01:55 UTC (commit
253cebc) está em state=ERROR — 15 deploys consecutivos. Promogifts.com.br está servindo um bundle JavaScript de antes desse horário. Tudo o que foi mergeado depois (PRs #599, #600, #601, #602 + edições visuais do Lovable) não está no ar.Este PR destrava o pipeline.
🔎 Diagnóstico
Dois arquivos no
mainreferenciavam símbolos que nunca foram exportados pelos módulos que dizem importar:Bug A —
src/contexts/ProductsContext.tsxO commit
8818bea("fix(pr#596 2/10): ProductsContext — simplify HMR guard") tinha título inocente mas reescreveu o arquivo inteiro, removendo 4 exports que 21 consumidores dependem:export const ProductsContextuseSearch.ts,useSellerCartsPage.tsexport function useProductsContext()export function useProductsContextSafe()FloatingCompareBar.tsxexport function ProductsProvider()AppProviders.tsx+ 2 testesAPI rica também sumiu:
getProductById,getProductsByIds,registerProducts, batched fetch com janela de 50 ms, HMR recovery viasetKey.Bug B —
src/lib/query-config.ts7 símbolos importados pelo
mainjamais foram escritos:QUERY_KEY_PREFIXESusePrefetchProduct.ts+ 2 testesPRODUTOS_QUERY_OPTIONSusePrefetchProduct.ts+ 2 testesTECNICAS_QUERY_OPTIONSuseTecnicasList.ts+ 2 testesTABELAS_PRECO_QUERY_OPTIONSuseTabelasPreco.ts+ 2 testesSTABLE_DATA_QUERY_OPTIONSgetStaleTimeForKeygetGcTimeForKeyCACHE_TIMESuseExternalCategoriesQuery.ts+ 2 testesGC_TIMESuseExternalCategoriesQuery.ts+ 2 testesO Rollup pára no primeiro import não resolvido, então cada deploy só revelava um bug por vez.
✏️ O que esse PR faz
src/contexts/ProductsContext.tsx— restauração verbatimRestaura o arquivo do commit
253cebc(último deploy READY):API completa:
products,isLoading,getProductById,getProductsByIds,registerProducts, batched fetch com janela de 50 ms, HMR module-duplication guard viaSymbol.for.src/lib/query-config.ts— adiciona os 9 exports faltantesValores pinados pelos testes existentes em
tests/lib/query-config*.test.ts:createQueryClientagora também auto-roteiagcTimevia observer noqueryCache, não sóstaleTime.✅ Validações
TypeScript --strict: 0 erros novos. (Os 2 erros pré-existentes de
stockStatus === 'critical'noProductCardImage.tsxsão dead-code anterior — não introduzidos por este PR.)35 / 35 testes funcionais locais cobrindo a API exata pinada por
tests/lib/query-config.test.tsetests/lib/query-config-extended.test.ts:CACHE_TIMESgetStaleTimeForKeygetGcTimeForKeycreateQueryClient*_QUERY_OPTIONSQUERY_KEY_PREFIXES33 / 33 testes funcionais locais cobrindo
ProductCardImage(hover crossfade),getProductsByIds(batch lookup),isChunkLoadErrore integração comFloatingCompareBarsimulado.Vercel build do branch: deploy
dpl_2N4bFf5LsJgpuY7MFqZDNbvUPvT6em commitb8c186→ state=READY ✅ (primeiro READY desde 16:01 UTC).HTTP real: 20/20 URLs
set_image_urldo banco transformadas para/cardretornam 200 OK image/jpeg (validação do PR #601 já mergeado).📦 Commits incluídos
17cec9a— primeira tentativa parcial (apenasuseProductsContextSafe+getProductsByIds) → expôs Bug A24fe64f2— restauração completa doProductsContext.tsx→ expôs Bug B19536c79— adicionaCACHE_TIMES/GC_TIMES→ expôs Bug B2 (mais 7 símbolos)b8c186— adiciona os 7 símbolos restantes → build READY ✅🎯 Impacto pós-merge
Ao mergear, o próximo deploy production destrava na cadeia:
v_products_publiccomset_image_urlno arProductCardImage.tsx(stockStatus === 'critical'— dead branch). São warnings, não bloqueantes do build.src/components/catalog/ProductCardImage.tsx(limpeza fica para outro PR).Summary by cubic
Restores the
ProductsContextAPI and adds missing exports inquery-configto fix build errors and unblock production deploys. Reinstates lazy product fetching and query caching presets used across the app.ProductsContext: restored prior API and exports (ProductsContext,ProductsProvider,useProductsContext,useProductsContextSafe) withgetProductById,getProductsByIds,registerProducts, 50ms batched fetch, and HMR duplication guard.query-config: addedCACHE_TIMES,GC_TIMES,QUERY_KEY_PREFIXES,getStaleTimeForKey,getGcTimeForKey, and presets (PRODUTOS_QUERY_OPTIONS,TECNICAS_QUERY_OPTIONS,TABELAS_PRECO_QUERY_OPTIONS,STABLE_DATA_QUERY_OPTIONS);QueryClientnow auto-routes per-keystaleTimeandgcTime.Written for commit b8c1860. Summary will update on new commits.
Summary by CodeRabbit
Release Notes