Skip to content

Lovable sync 1780254773#549

Closed
adm01-debug wants to merge 23 commits into
mainfrom
lovable-sync-1780254773
Closed

Lovable sync 1780254773#549
adm01-debug wants to merge 23 commits into
mainfrom
lovable-sync-1780254773

Conversation

@adm01-debug
Copy link
Copy Markdown
Owner

@adm01-debug adm01-debug commented May 31, 2026

📋 Descrição

🎯 Tipo de mudança

  • 🚀 feat — nova funcionalidade
  • 🐛 fix — correção de bug
  • ♻️ refactor — refatoração (sem mudança de comportamento)
  • 🔧 chore — manutenção, deps, config
  • 📚 docs — documentação
  • ⚡ perf — performance
  • 🔒 security — segurança
  • 🚨 hotfix — correção urgente em produção
  • 💥 breaking change — quebra compatibilidade

🔗 Issues relacionadas

Closes #
Refs #

🌐 Sistemas afetados

  • Bitrix24 (CRM, SPAs, BizProc)
  • Supabase (DB, Edge Functions, RLS, migrations)
  • n8n (workflows)
  • Evolution API / WhatsApp
  • Bling (NFe, OAuth)
  • Cloudflare (Workers, Images, Tunnels)
  • Frontend (UI, dashboards)
  • CI / GitHub Actions
  • Outro: ____

🧪 Como testar

✅ Checklist pré-merge

Qualidade

  • Código segue style guide (ESLint passa)
  • npx tsc --noEmit passa sem erros
  • Testes passam (npm run test)
  • Adicionei testes para novas funcionalidades quando aplicável
  • CodeRabbit revisou o PR (ou justificativa para skip)

Segurança

  • Sem secrets, tokens ou credenciais hardcoded
  • Variáveis de ambiente novas documentadas
  • Sem console.log com payloads sensíveis (usar logger.*)
  • RLS revisado se houve mudança em tabelas
  • Edge functions: input validado com Zod

Documentação

  • Atualizei docs (README / CHANGELOG / docs/) se necessário
  • Memória atualizada (mem://) se a mudança afetar arquitetura/regras
  • Migrations com backup em _backup_*_YYYYMMDD se destrutivas

UI

  • Componentes usam tokens semânticos (sem cores hardcoded)
  • Screenshots / vídeo anexados (se mudança visual)

📸 Screenshots (se UI)

🔄 Plano de rollback

⚠️ Notas para o reviewer


Summary by cubic

Virtualized and paginated novelty grid with improved accessibility and stable skeletons to prevent layout shift. Hardened stock dashboard error handling to avoid white screens, and expanded E2E visual/a11y coverage with sharded CI runs.

  • New Features

    • Virtualized novelty grid using @tanstack/react-virtual with 20 items per page and smooth scrolling.
    • Accessibility: roles, keyboard support, ARIA labels; improved header semantics and test IDs.
    • Stable skeletons: fixed min-heights for card titles/prices and a loading skeleton for the stock table.
    • CI/tests: sharded Playwright visual runs, workers=2 in CI, new visual/a11y specs with @playwright/test and axe-core/playwright.
    • REST native whitelist updated to include variant_supplier_sources, supplier_branches, and categories.
  • Bug Fixes

    • Stock dashboard no longer shows a white screen on bridge failures; clear error message with retry, and better 410 Gone handling in the fetcher (useVariantStock now surfaces errors).
    • Added a regression test for /estoque to ensure reliable load and filtering behavior.

Written for commit 49ba02b. Summary will update on new commits.

Review in cubic

lovable-dev Bot and others added 23 commits May 31, 2026 18:47
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
X-Lovable-Edit-ID: edt-2861a40d-d258-4d19-aa54-31352a773f86
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
X-Lovable-Edit-ID: edt-dcfc3e5b-15a6-4368-9747-c1e2a3a946b2
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
X-Lovable-Edit-ID: edt-a613bb01-3be5-4e73-afb3-e58acf636175
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
X-Lovable-Edit-ID: edt-6fa19df9-0496-4719-b1c5-a88eab685b36
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
X-Lovable-Edit-ID: edt-70a1ae4f-e6ec-41f0-873e-f30970596f1d
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
X-Lovable-Edit-ID: edt-f6465b77-0411-4059-9764-f972bb93fbed
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
X-Lovable-Edit-ID: edt-ad1f9ed9-044b-4064-a71b-751803bc9545
Co-authored-by: adm01-debug <231131902+adm01-debug@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 31, 2026 19:58
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Warning

Review limit reached

@adm01-debug, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 37 minutes and 3 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d6468072-5122-4ff1-8a9b-fc8571085a79

📥 Commits

Reviewing files that changed from the base of the PR and between 3d681b1 and 49ba02b.

📒 Files selected for processing (16)
  • .github/workflows/visual-tests.yml
  • e2e/routes/app/novelty-card-variations.spec.ts
  • e2e/routes/app/novelty-grid-visual.spec.ts
  • playwright.config.ts
  • src/components/inventory/StockDashboard.tsx
  • src/components/inventory/VariantStockTable.tsx
  • src/components/loading/ModernSkeletons.tsx
  • src/components/novelties/NoveltyCards.tsx
  • src/components/novelties/NoveltyProductGrid.tsx
  • src/components/novelties/VirtualizedNoveltyGrid.tsx
  • src/hooks/products/useVariantStock.ts
  • src/hooks/stock/stockFetcher.ts
  • src/lib/external-db/rest-native.ts
  • src/pages/auth/Auth.tsx
  • src/pages/products/NoveltiesPage.tsx
  • tests/e2e/stock-regression.spec.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch lovable-sync-1780254773

Comment @coderabbitai help to get the list of available commands and usage tips.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 31, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
we-dream-big Ready Ready Preview, Comment May 31, 2026 7:58pm

@supabase
Copy link
Copy Markdown

supabase Bot commented May 31, 2026

This pull request has been ignored for the connected project doufsxqlfjyuvxuezpln because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the /novidades (novelties) page to use @tanstack/react-virtual with client-side pagination (20/page) and tightens accessibility/skeletons, and hardens the /estoque (stock) dashboard against legacy "bridge" failures by surfacing the React Query error instead of silently breaking. It also expands the Playwright visual/a11y suite (sharded in CI) and whitelists three more tables for the REST‑native external DB path.

Changes:

  • New VirtualizedNoveltyGrid + paginated NoveltyProductGrid; redesigned header and a11y/keyboard support on NoveltyGridCard; stable skeleton heights.
  • StockDashboard shows a "Falha ao carregar estoque" error card with retry; useVariantStock returns error with retry/backoff; fetchPaginatedFromBridge throws (with friendly 410 message) instead of swallowing errors; VariantStockTable gains an isLoading skeleton mode.
  • E2E: new novelty visual/a11y/variation specs, new /estoque regression spec, Playwright workers=2 in CI, sharded visual-baseline workflow; REST native whitelist += variant_supplier_sources, supplier_branches, categories.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 19 comments.

Show a summary per file
File Description
src/components/novelties/VirtualizedNoveltyGrid.tsx New virtualized grid using tanstack/react-virtual.
src/components/novelties/NoveltyProductGrid.tsx Switch to virtualized grid + add pagination UI; bump fetch limit to 400.
src/components/novelties/NoveltyCards.tsx Add keyboard/ARIA props and memoized click handler to grid card.
src/components/loading/ModernSkeletons.tsx Fix skeleton min-heights to match real card layout.
src/components/inventory/VariantStockTable.tsx Add isLoading skeleton table mode.
src/components/inventory/StockDashboard.tsx Render error card with retry; pass isFetching to table skeleton.
src/hooks/products/useVariantStock.ts Expose error; add retry: 3 with exponential backoff.
src/hooks/stock/stockFetcher.ts Throw friendly 410 message on bridge error instead of break.
src/lib/external-db/rest-native.ts Whitelist 3 additional tables for REST-native reads.
src/pages/products/NoveltiesPage.tsx Restyle header; add novelty-description testid.
src/pages/auth/Auth.tsx Remove SupabaseConnectionDebug import/usage.
playwright.config.ts Increase CI workers from 1 to 2.
.github/workflows/visual-tests.yml Shard visual baseline runs (2 shards); add 2 new specs.
tests/e2e/stock-regression.spec.ts New /estoque regression spec.
e2e/routes/app/novelty-grid-visual.spec.ts New visual + axe-core a11y spec across viewports.
e2e/routes/app/novelty-card-variations.spec.ts New card-variation visual spec with mocked novelties API.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +569 to +616
if (isLoading) {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[300px]">Produto</TableHead>
<TableHead className="hidden md:table-cell">Cores</TableHead>
<TableHead>Estoque Total</TableHead>
<TableHead className="hidden sm:table-cell w-[120px]">Progresso</TableHead>
<TableHead className="hidden lg:table-cell">Reservado</TableHead>
<TableHead>Disponível</TableHead>
<TableHead className="hidden md:table-cell">Trânsito</TableHead>
<TableHead>Status</TableHead>
<TableHead className="hidden sm:table-cell">Previsão</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{[...Array(10)].map((_, i) => (
<TableRow key={i}>
<TableCell>
<div className="flex items-center gap-2">
<div className="h-6 w-6 rounded-md bg-muted animate-pulse" />
<div className="space-y-2">
<div className="h-4 w-32 bg-muted animate-pulse rounded" />
<div className="h-3 w-20 bg-muted animate-pulse rounded" />
</div>
</div>
</TableCell>
<TableCell className="hidden md:table-cell">
<div className="flex gap-1">
{[...Array(3)].map((_, j) => (
<div key={j} className="h-5 w-5 rounded-full bg-muted animate-pulse" />
))}
</div>
</TableCell>
<TableCell><div className="h-4 w-12 bg-muted animate-pulse rounded" /></TableCell>
<TableCell className="hidden sm:table-cell"><div className="h-2 w-full bg-muted animate-pulse rounded" /></TableCell>
<TableCell className="hidden lg:table-cell"><div className="h-4 w-8 bg-muted animate-pulse rounded" /></TableCell>
<TableCell><div className="h-4 w-12 bg-muted animate-pulse rounded" /></TableCell>
<TableCell className="hidden md:table-cell"><div className="h-4 w-8 bg-muted animate-pulse rounded" /></TableCell>
<TableCell><div className="h-6 w-20 bg-muted animate-pulse rounded-full" /></TableCell>
<TableCell className="hidden sm:table-cell"><div className="h-4 w-10 bg-muted animate-pulse rounded" /></TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
}
Comment on lines +603 to 649
</div>

{totalPages > 1 && (
<div className="mt-6 flex justify-center py-4">
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
className={cn(currentPage === 1 && "pointer-events-none opacity-50", "cursor-pointer")}
/>
</PaginationItem>

{Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => {
// Simplificado: mostra todas as páginas se forem poucas, senão mostra com ellipsis
// Aqui vamos mostrar apenas as 5 primeiras para o teste ou uma lógica simples
if (totalPages > 7) {
if (page !== 1 && page !== totalPages && Math.abs(page - currentPage) > 1) {
if (page === 2 || page === totalPages - 1) return <PaginationItem key={page}><PaginationEllipsis /></PaginationItem>;
return null;
}
}

return (
<PaginationItem key={page}>
<PaginationLink
isActive={currentPage === page}
onClick={() => setCurrentPage(page)}
className="cursor-pointer"
>
{page}
</PaginationLink>
</PaginationItem>
);
})}

<PaginationItem>
<PaginationNext
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
className={cn(currentPage === totalPages && "pointer-events-none opacity-50", "cursor-pointer")}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</div>
)}
</motion.div>
Comment on lines +29 to +73
const numCols = useResponsiveColumns(gridColumns);
const rowCount = Math.ceil(products.length / numCols);

const virtualizer = useVirtualizer({
count: rowCount,
getScrollElement: () => parentRef.current,
estimateSize: () => 480,
overscan: 3,
measureElement: (el) => el.getBoundingClientRect().height,
});

const effectiveCols = gridColumns;

return (
<div
ref={parentRef}
className="overflow-auto"
style={{ maxHeight: 'calc(100vh - 280px)' }}
role="list"
aria-label="Grade de novidades"
>
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map((virtualRow) => {
const startIdx = virtualRow.index * numCols;
const rowProducts = products.slice(startIdx, startIdx + numCols);

return (
<div
key={virtualRow.key}
data-index={virtualRow.index}
ref={virtualizer.measureElement}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualRow.start}px)`,
}}
className={`grid ${getGridColsClass(effectiveCols)} ${getGridGapClass(effectiveCols)} pb-8`}
Comment on lines +42 to +49
return (
<div
ref={parentRef}
className="overflow-auto"
style={{ maxHeight: 'calc(100vh - 280px)' }}
role="list"
aria-label="Grade de novidades"
>
Comment on lines +93 to +103
onClick={handleClick}
role="article"
aria-label={`${product.product_name} — ${getStockStatusLabel(stockStatus)}, ${formatPrice(product.base_price ?? 0)}`}
aria-selected={selectionMode ? isSelected : undefined}
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleClick();
}
}}
Comment on lines +39 to +196
for (const viewport of viewports) {
test.describe(`Viewport: ${viewport.name}`, () => {
test.use({ viewport: { width: viewport.width, height: viewport.height } });

test('Header Immediate Rendering & Visual', async ({ page }) => {
await page.goto('/novidades', { waitUntil: 'domcontentloaded' });

const header = page.locator('div.flex.flex-col.gap-4').first();
const title = header.locator('[data-testid="page-title-novidades"]');
const desc = header.locator('[data-testid="novelty-description"]');

await expect(title).toBeVisible();
await expect(title).toHaveText('Novidades');
await expect(desc).toHaveText('Produtos recém-chegados ao catálogo nos últimos 30 dias');

await expect(header).toHaveScreenshot(`novelty-header-only-${viewport.name}.png`);
});

test('Grid Visual Regression & Scroll', async ({ page }) => {
await page.goto('/novidades');
const grid = page.locator('div[role="list"]');
await grid.waitFor({ state: 'visible' });
await page.waitForTimeout(1000);

await expect(grid).toHaveScreenshot(`novelty-grid-initial-${viewport.name}.png`, {
maxDiffPixelRatio: 0.02,
});

await grid.evaluate(el => el.scrollTop = 1000);
await page.waitForTimeout(800);

await expect(grid).toHaveScreenshot(`novelty-grid-scrolled-${viewport.name}.png`, {
maxDiffPixelRatio: 0.02,
});
});

test('Accessibility Scan', async ({ page }) => {
await page.goto('/novidades');
const grid = page.locator('div[role="list"]');
await grid.waitFor({ state: 'visible' });

const results = await new AxeBuilder({ page })
.include('div[role="list"]')
.analyze();

if (results.violations.length > 0) {
console.error(`A11y Violations for viewport ${viewport.name}:`);
results.violations.forEach(v => {
console.error(`- [${v.id}] ${v.help} (${v.impact})`);
console.error(` URL: ${v.helpUrl}`);
console.error(` Nodes: ${v.nodes.length}`);
});
}

expect(results.violations, `A11y violations found in ${viewport.name}: ${results.violations.map(v => v.id).join(', ')}`).toEqual([]);
});

test('Browser Preferences - Accessibility Consistency', async ({ page }) => {
// Simulate high contrast / large font via CSS injection
await page.addStyleTag({
content: `
html { font-size: 20px !important; }
* { transition: none !important; animation: none !important; }
`
});

await page.goto('/novidades');
const grid = page.locator('div[role="list"]');
await grid.waitFor({ state: 'visible' });

await expect(grid).toHaveScreenshot(`novelty-grid-a11y-prefs-${viewport.name}.png`);
});

test('Keyboard Navigation', async ({ page }) => {
await page.goto('/novidades');
await page.keyboard.press('Tab');

const activeElement = await page.evaluate(() => document.activeElement?.tagName);
expect(activeElement).toBeDefined();

await page.keyboard.press('Tab');
await expect(page).toHaveScreenshot(`novelty-keyboard-focus-${viewport.name}.png`);
});
});
}

test('Skeleton State & Layout Stability', async ({ page }) => {
// Intercept with delay to see skeleton
await page.route('**/api/external-db', async route => {
if (route.request().postDataJSON()?.operation === 'select') {
await new Promise(resolve => setTimeout(resolve, 2000));
}
await route.continue();
});

await page.goto('/novidades');
const skeleton = page.locator('.animate-spin').first();
await expect(skeleton).toBeVisible();

// Capture skeleton grid
const grid = page.locator('div.grid').filter({ has: page.locator('.animate-pulse') }).first();
await expect(grid).toHaveScreenshot('novelty-skeleton-state.png');

// Wait for data and check stability
await page.waitForSelector('div[role="list"]');
const realGrid = page.locator('div[role="list"]');
await expect(realGrid).toBeVisible();
await expect(realGrid).toHaveScreenshot('novelty-data-loaded-stability.png');
});

test('Pagination & Alignment Check', async ({ page }) => {
// Mock enough products for multiple pages
await page.route('**/api/external-db', async route => {
const body = route.request().postDataJSON();
if (body?.operation === 'select' && body?.table === 'products') {
const mockProducts = Array.from({ length: 45 }, (_, i) => ({
id: `page-mock-${i}`,
name: `Product ${i} ${i % 3 === 0 ? 'with a very very very long name to test wrapping and alignment consistency across the grid' : ''}`,
sku: `SKU-${i}`,
primary_image_url: null,
sale_price: i % 5 === 0 ? null : 100 + i,
category_id: 'cat-1',
supplier_id: 'sup-1',
created_at: new Date().toISOString(),
stock_quantity: 100,
min_quantity: 10
}));
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ records: mockProducts, count: 45 })
});
} else {
await route.continue();
}
});

await page.goto('/novidades');
const paginator = page.locator('nav[aria-label="pagination"]');
await paginator.waitFor();

// Check first page alignment
const firstPageItems = page.locator('div[role="listitem"]');
await expect(firstPageItems).toHaveCount(20);
await expect(page).toHaveScreenshot('novelty-pagination-page-1.png');

// Click next
await page.click('a[aria-label="Go to next page"]');
await page.waitForTimeout(500);
await expect(page.locator('div[role="listitem"]')).toHaveCount(20);
await expect(page).toHaveScreenshot('novelty-pagination-page-2.png');

// Click last page (3)
await page.click('a:text("3")');
await page.waitForTimeout(500);
await expect(page.locator('div[role="listitem"]')).toHaveCount(5);
await expect(page).toHaveScreenshot('novelty-pagination-last-page.png');
});
Comment on lines +43 to +62
test('Header Immediate Rendering & Visual', async ({ page }) => {
await page.goto('/novidades', { waitUntil: 'domcontentloaded' });

const header = page.locator('div.flex.flex-col.gap-4').first();
const title = header.locator('[data-testid="page-title-novidades"]');
const desc = header.locator('[data-testid="novelty-description"]');

await expect(title).toBeVisible();
await expect(title).toHaveText('Novidades');
await expect(desc).toHaveText('Produtos recém-chegados ao catálogo nos últimos 30 dias');

await expect(header).toHaveScreenshot(`novelty-header-only-${viewport.name}.png`);
});

test('Grid Visual Regression & Scroll', async ({ page }) => {
await page.goto('/novidades');
const grid = page.locator('div[role="list"]');
await grid.waitFor({ state: 'visible' });
await page.waitForTimeout(1000);

Comment on lines +64 to +66
await expect(grid).toHaveScreenshot('novelty-card-variations.png', {
maxDiffPixelRatio: 0.05
});
'product_relationships', 'product_groups', 'product_group_members',
'v_price_history_safe',
'system_kill_switches',
'variant_supplier_sources', 'supplier_branches', 'categories',
Comment on lines +63 to +72
await expect(grid).toHaveScreenshot(`novelty-grid-initial-${viewport.name}.png`, {
maxDiffPixelRatio: 0.02,
});

await grid.evaluate(el => el.scrollTop = 1000);
await page.waitForTimeout(800);

await expect(grid).toHaveScreenshot(`novelty-grid-scrolled-${viewport.name}.png`, {
maxDiffPixelRatio: 0.02,
});
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 49ba02bbc1

ℹ️ 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".

}: VariantStockTableProps & { isLoading?: boolean }) {
const [expandedProducts, setExpandedProducts] = useState<Set<string>>(new Set());

if (isLoading) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep hooks before the loading return

When isFetching becomes true after this table has already rendered data (for example after fetchStockData invalidates the query), this early return skips the later useState, useSearchParams, useRef, useEffect, and useMemo calls. React will then detect a different hook order and can throw instead of showing the stock table; move the loading branch after the hooks or render the skeleton without returning before them.

Useful? React with 👍 / 👎.

</div>
</div>

{totalPages > 1 && (
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Render pagination outside the fetching overlay

This pagination block is nested inside the isFetching && products.length > 0 overlay that starts above it, so once the novelty query settles the page controls disappear. With more than 20 novelties, users only see the first page after loading and cannot navigate to the remaining results; move the pagination outside the AnimatePresence/fetching conditional.

Useful? React with 👍 / 👎.


test('Pagination & Alignment Check', async ({ page }) => {
// Mock enough products for multiple pages
await page.route('**/api/external-db', async route => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mock the endpoint the novelties page actually calls

These fixtures are registered on **/api/external-db, but /novidades loads data through useNoveltiesWithDetails/invokeExternalDb: products is REST-native eligible and otherwise falls back to functions/v1/external-db-bridge, not this URL (the same mismatch appears in the new card-variations spec's functions/v1/novelties mock). In CI the 45 mocked products are never supplied, so the paginator/count assertions depend on live or empty data and will fail or become flaky; route the actual Supabase REST/bridge request instead.

Useful? React with 👍 / 👎.

);
}

if (error) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve stale stock data on refresh errors

When a refresh fails after the dashboard has already loaded data, React Query can keep the previous productStocks while also exposing the refetch error. This unconditional error return then replaces the entire stock dashboard with the failure card after a transient/manual refresh failure, even though usable stale data is still available; only show the full-page error when there is no loaded data, or surface background refetch errors non-destructively.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Closing in favour of #553, which implements all features from this PR (and from #547, #550, #551, #552) on current main with three critical bugs fixed that were present across the Lovable-generated set:

  1. React Rules of Hooks violation in VariantStockTable — early return was placed between hook calls.
  2. Pagination trapped inside isFetching overlay in NoveltyProductGrid — pagination was invisible during normal browsing.
  3. Ellipsis logic collapsed navigable page buttons — fixed to use currentPage ± 2 boundary.

This branch also had merge conflicts with main due to the older base SHA.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants