KwadMarket Docs
Remediation Plan

Frontend

Frontend maintenance — implementation spec for plan phase 8

Feeds plan phase 8. The apps/web refactor is done (features/ structure, TanStack Query, RHF+zod, middleware auth, HttpOnly-cookie BFF, socket realtime, next/image, tests + CI). What's left is maintenance, found by the 2026-07-02 review — ordered below, one PR per step.

1. Quick wins (no behavior change)

  • lib/api/server.ts:24-29: the error message parsed from the response body is never used — include it in the thrown Error so server components stop losing backend error details.
  • Dead historyRef in both review screens (features/admin/products/components/product-review-content.tsx:42,113,128, features/admin/scraper/components/scraped-product-review-content.tsx:24,59,78): collected but never read — the "Back" button doesn't restore the previous status. Either implement undo or delete the ref.
  • Fix the 8 eslint warnings: react-hooks/set-state-in-effect ×6 (confirm-dialog.tsx:58, edit-product-form.tsx:92, admin-scraper-content.tsx:44, use-picker-search.ts:19-20, use-chat.tsx:76), no-img-element (hero-section.tsx:72), unused eslint-disable (image-upload.tsx:39).
  • Root README.md stack table: replace "GraphQL (code-first)" / "Apollo Client" with REST + TanStack Query (stale since the GraphQL removal).

2. Formatter

No Prettier → recently merged files use double-quotes+semicolons (product-review-content.tsx, use-product-filters.ts) while the rest is single-quote/no-semi. Add Prettier (or Biome) + one format-only commit + CI check. Do this before step 3 so the refactor lands formatted.

3. Review-queue refactor (the one structural item)

product-review-content.tsx (453 lines) and scraped-product-review-content.tsx are ~80% the same pattern, both regressed to effect-fetching:

  • Effect-fetch + serial load: loadQueue() in useEffect with manual loading flags loops up to 20 pages × 50 items sequentially before rendering (product-review-content.tsx:76-101; same shape at scraped-product-review-content.tsx:49). Replace with a paginated/infinite useQuery fetching ahead as the reviewer advances.
  • No cache invalidation: status changes call adminProductsApi.updateProduct directly instead of features/admin/products/mutations.tsqk.products.* never invalidates, admin lists go stale after a review session.
  • Auto-validate survives unmount (product-review-content.tsx:149-193): the loop keeps mutating statuses and calling setState after navigation; only the Stop button sets stopAutoRef. Add an AbortController/unmount guard.
  • Extract a shared useReviewQueue hook (queue, index, progress, undo history, auto-advance) consumed by both screens; split product-review-content.tsx into hook + header + duplicates panel + action bar, each ≤250 lines.

4. Dedup

  • features/admin/deals/components/deal-status-pill.tsx re-declares the deal status map that features/deals/constants.ts owns — and they've diverged (constants.ts:25 uses raw yellow-500, the pill uses warning tokens). Fold into DealStatusBadge/constants, keyed on the Prisma enum type instead of Record<string, …>.
  • Two create-product dialogs: features/deals/components/product-picker/create-product-dialog.tsx (218 lines, multi-useState — violates the RHF rule) vs features/admin/products/components/create-product-dialog.tsx (136, RHF). Converge on one RHF component.

5. Consistency sweep

  • Inline query keys ['scraper', 'products'] in ~10 spots (scraped-product-row.tsx, use-bulk-actions.ts, scraper-results-toolbar.tsx) → qk.scraper.products; add the missing qk.scraper.bulkJobs key.

  • Raw Tailwind palette (text-amber-600, bg-red-50, text-green-500…) in ~10 admin files / ~48 occurrences → tokens (text-warning, bg-success/10…). Extend the CI color gate to raw palette classes so they can't return:

    ! grep -rnE "(text|bg|border)-(amber|green|red|blue|yellow)-[0-9]" apps/web/app apps/web/features apps/web/components --include="*.tsx"
  • formatPrice bypassed with Number(price).toFixed(0) in 6 files: app/(guest)/deal/[id]/page.tsx:21, features/admin/deals/components/{deal-detail-view.tsx:90, queue-row.tsx:17, admin-deals-content.tsx:124}, features/admin/products/components/admin-product-detail.tsx:189, features/profile/components/profile-deal-row.tsx:51.

6. Build hermeticity + Next upkeep

  • next build fails without a live backend — fixed: the public cached lookups (getCategories, getRootCategories, getBrands, getSpecTypes, getRecentDeals in lib/api/server.ts) go through serverFetchWithFallback, which logs and falls back to [] instead of throwing. / and /products prerender with empty sections when the API is unreachable at build time; ISR (revalidate 60s/300s) fills in real data once the API is up. build-backend/build-frontend in CI now depend on lint, not test, so they run independently of the (currently broken) test job.
  • Next 16 deprecation: rename middleware.tsproxy.ts file convention with the next Next bump.

7. Test expansion (ongoing)

  • Add MSW; first targets: the new useReviewQueue hook and use-bulk-actions (highest churn — both review screens regressed unnoticed), then queries/mutations invalidation behavior.
  • One admin e2e flow (product review), and keep e2e selectors scoped (a global getByPlaceholder(/search/i) broke when header search shipped — see plan phase 0).

On this page