feat(scanner): desktop UX polish round 2 — 10 improvements#951
feat(scanner): desktop UX polish round 2 — 10 improvements#951ericsocrat merged 4 commits intomainfrom
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Scanner page: - R1: timeout banner only fires after camera feed is confirmed active - R5: camera hint renders above timeout banner for visual priority Scan result (not found): - R2: max-w-md wrapper centers content on wide viewports - R6: PackageSearch icon replaces generic Search icon - R7: GS1 coverage note explains regional database gaps Submit page: - R3: country explainer text below country badge - R4: optional labels on Brand, Category, Photo, Notes fields - R8: character counter (0/500) on notes textarea - R9: enlarged photo upload drop zone with centered layout - R10: step indicator (Step 2 of 2) below page subtitle i18n: 4 new keys added to en/de/pl dictionaries Tests: 7 new tests + 8 label selectors updated for optional suffix Docs: DESIGN_REFRESH_SPEC.md added (audit planning artifact)
Bundle Size Report
✅ Bundle size is within acceptable limits. |
644c131 to
e1be919
Compare
There was a problem hiding this comment.
Pull request overview
Polishes the desktop scanner UX across the scan page, not-found scan result, and product submission flow, and adds supporting i18n keys + test updates.
Changes:
- Refines scanner timeout behavior (only starts once the camera feed is active) and adjusts hint ordering.
- Improves not-found view desktop layout/iconography and adds a GS1 coverage note.
- Enhances submit form UX (optional markers, country explainer, notes counter, larger photo drop zone, step indicator) with updated tests and locale strings.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/components/scan/ScanResultView.tsx | Desktop layout constraint for not-found view; icon swap; GS1 coverage note rendering. |
| frontend/src/components/scan/ScanResultView.test.tsx | Adds assertions for GS1 coverage note presence/absence. |
| frontend/src/app/app/scan/submit/page.tsx | Adds step indicator, optional labels, country explainer, larger photo drop zone, notes counter. |
| frontend/src/app/app/scan/submit/page.test.tsx | Updates selectors for optional suffix; adds tests for new submit-page UX elements. |
| frontend/src/app/app/scan/page.tsx | Gates 15s timeout on feedActive; reorders camera hint vs timeout banner. |
| frontend/src/app/app/scan/page.test.tsx | Updates timeout test to simulate feedActive=true before asserting timer registration. |
| frontend/messages/en.json | Adds common.optional, scan.gs1CoverageNote, submit.countryExplainer, submit.stepIndicator. |
| frontend/messages/de.json | Same new keys for DE locale. |
| frontend/messages/pl.json | Same new keys for PL locale. |
| docs/DESIGN_REFRESH_SPEC.md | Adds design/UX refresh planning spec document. |
| - `common.retry` — Shared button label | ||
|
|
||
| **Rules:** | ||
| - 3-segment keys minimum. Never `retry` alone — always `common.retry`. | ||
| - Boolean conditions use `{key}Yes` / `{key}No` suffix, not separate keys. | ||
| - Counts use ICU `{count, plural, one {# product} other {# products}}` (via `next-intl` or manual interpolation). |
| <label | ||
| htmlFor="photo" | ||
| className="mb-1 block text-sm font-medium text-foreground-secondary" | ||
| > | ||
| {t("submit.photoLabel")} | ||
| {t("submit.photoLabel")}{" "} | ||
| <span className="font-normal text-foreground-muted">{t("common.optional")}</span> | ||
| </label> | ||
| {photoPreview && photoPreview.startsWith("blob:") ? ( | ||
| <div className="relative inline-block"> | ||
| <img | ||
| src={photoPreview} | ||
| alt="" | ||
| className="h-32 w-32 rounded-lg border border-border object-cover" | ||
| /> | ||
| <button | ||
| type="button" | ||
| onClick={removePhoto} | ||
| className="absolute -right-2 -top-2 rounded-full bg-red-500 p-0.5 text-white shadow-sm hover:bg-red-600" | ||
| aria-label={t("submit.photoRemove")} | ||
| > | ||
| <X size={14} /> | ||
| </button> | ||
| </div> | ||
| ) : ( | ||
| <label | ||
| htmlFor="photo" | ||
| className="flex cursor-pointer items-center gap-2 rounded-lg border border-dashed border-border px-4 py-3 text-sm text-foreground-secondary hover:border-brand hover:text-brand" | ||
| className="flex cursor-pointer flex-col items-center justify-center gap-2 rounded-lg border border-dashed border-border px-4 py-8 text-sm text-foreground-secondary hover:border-brand hover:text-brand" | ||
| > | ||
| <Camera size={18} aria-hidden="true" /> | ||
| <Camera size={24} aria-hidden="true" /> | ||
| {t("submit.photoHint")} | ||
| </label> |
There was a problem hiding this comment.
Pull request overview
Desktop UX polish pass for the scanner flow (camera scan page, “not found” results, and product submission), plus supporting i18n/test updates and an added design refresh spec document.
Changes:
- Adjusts scanner timeout behavior to only start after the camera feed is confirmed active, and tweaks hint/banner ordering on the scan page.
- Improves “not found” result layout on desktop and adds a GS1 coverage explanatory note (with tests).
- Polishes the submit flow UI (optional field indicators, country explainer, notes counter, photo dropzone sizing, step indicator) with updated translations and tests.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/components/scan/ScanResultView.tsx | Centers not-found content on desktop, swaps icon, adds GS1 coverage note copy. |
| frontend/src/components/scan/ScanResultView.test.tsx | Adds assertions for GS1 coverage note presence/absence. |
| frontend/src/app/app/scan/submit/page.tsx | Adds step indicator, optional field suffixes, country explainer, notes counter, and larger photo dropzone. |
| frontend/src/app/app/scan/submit/page.test.tsx | Updates label selectors and adds tests for new submit-page UX elements. |
| frontend/src/app/app/scan/page.tsx | Gates timeout on feedActive and reorders camera hint vs timeout banner. |
| frontend/src/app/app/scan/page.test.tsx | Updates timeout test to simulate feedActive=true before asserting timer registration. |
| frontend/messages/en.json | Adds new i18n keys for optional label, GS1 coverage note, country explainer, step indicator. |
| frontend/messages/de.json | Same i18n additions for German locale. |
| frontend/messages/pl.json | Same i18n additions for Polish locale. |
| frontend/e2e/auth.setup.ts | Raises waitForURL timeout to reduce CI flakiness; minor import formatting change. |
| docs/INDEX.md | Adds the new design refresh spec to the docs index. |
| docs/DESIGN_REFRESH_SPEC.md | New design/UX planning spec document for the broader refresh effort. |
| All user-facing strings live in `/messages/{locale}.json`. Keys follow: | ||
|
|
||
| ``` | ||
| {domain}.{subject}.{action/descriptor} | ||
| ``` | ||
|
|
||
| Examples: | ||
| - `product.score.excellent` — Score band label | ||
| - `scanner.error.cameraBlocked` — Scanner error message | ||
| - `onboarding.step.region` — Onboarding step title | ||
| - `common.retry` — Shared button label | ||
|
|
||
| **Rules:** | ||
| - 3-segment keys minimum. Never `retry` alone — always `common.retry`. | ||
| - Boolean conditions use `{key}Yes` / `{key}No` suffix, not separate keys. | ||
| - Counts use ICU `{count, plural, one {# product} other {# products}}` (via `next-intl` or manual interpolation). |
| import { expect, test as setup } from "@playwright/test"; | ||
| import { | ||
| TEST_EMAIL, | ||
| TEST_PASSWORD, | ||
| ensureTestUser, | ||
| TEST_EMAIL, | ||
| TEST_PASSWORD, | ||
| ensureTestUser, | ||
| } from "./helpers/test-user"; |
3797e9d to
1b98ed8
Compare
1b98ed8 to
d259d7a
Compare
There was a problem hiding this comment.
Pull request overview
Implements a second round of desktop UX polish for the scanner flow (camera page, not-found result view, and product submission form), along with i18n updates, test adjustments, and supporting documentation.
Changes:
- Scanner page: gate the 15s timeout banner behind
feedActive, and reorder the camera hint above the timeout banner. - Not-found + submit UX: improve desktop layout/semantics (centering, icon swap, GS1 coverage note) and add submission form affordances (optional labels, country explainer, notes counter, step indicator, larger photo dropzone).
- Localization + tests + docs: add new i18n keys across locales, update/add unit tests, and add/index a design refresh spec document.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/components/scan/ScanResultView.tsx | Centers not-found view on desktop, swaps icon, adds GS1 coverage note text |
| frontend/src/components/scan/ScanResultView.test.tsx | Adds tests for GS1 coverage note presence/absence |
| frontend/src/app/app/scan/submit/page.tsx | Adds step indicator, optional markers, country explainer, notes counter, and larger upload drop zone |
| frontend/src/app/app/scan/submit/page.test.tsx | Updates label selectors for optional suffix + adds tests for new submit-page UI elements |
| frontend/src/app/app/scan/page.tsx | Starts scan-timeout timer only after feedActive; reorders camera hint above timeout messaging |
| frontend/src/app/app/scan/page.test.tsx | Updates timeout test to simulate feedActive=true before asserting timeout registration |
| frontend/messages/en.json | Adds new keys: common.optional, scan.gs1CoverageNote, submit.countryExplainer, submit.stepIndicator |
| frontend/messages/de.json | Adds same 4 i18n keys in German |
| frontend/messages/pl.json | Adds same 4 i18n keys in Polish |
| frontend/e2e/auth.setup.ts | Increases auth setup robustness via longer timeouts (and minor refactor) |
| docs/INDEX.md | Adds the new design refresh spec to docs index |
| docs/DESIGN_REFRESH_SPEC.md | Adds a design/UX refresh spec document for future planning |
| - 3-segment keys minimum. Never `retry` alone — always `common.retry`. | ||
| - Boolean conditions use `{key}Yes` / `{key}No` suffix, not separate keys. | ||
| - Counts use ICU `{count, plural, one {# product} other {# products}}` (via `next-intl` or manual interpolation). |
| TEST_EMAIL, | ||
| TEST_PASSWORD, | ||
| ensureTestUser, |
Build/seed steps use staging fallback (SUPABASE_URL_STAGING || NEXT_PUBLIC_SUPABASE_URL) but Mobile/Desktop Audit steps used only production secrets. When staging secrets exist, ensureTestUser() created the user on production while the app was built against staging — login silently failed because the user didn't exist on the staging Supabase instance. Fix: use the same staging-first fallback pattern in audit steps.
Summary
Implements all 10 recommendations (R1–R10) from the desktop UX audit conducted against the live production site (tryvit.vercel.app). These are targeted polish improvements to the scanner flow — camera page, not-found result view, and product submission form.
Changes by Recommendation
Scanner Page (
scan/page.tsx)feedActive === true), preventing false "camera not responding" warnings during initial camera negotiation.Scan Result — Not Found (
ScanResultView.tsx)ScanNotFoundViewwrapped inmax-w-md mx-autoto prevent the content from stretching across the full width on desktop.Searchicon withPackageSearchfrom lucide-react for better semantic match (product not found ≠ search).Submit Page (
submit/page.tsx)t("common.optional").{n}/500counter below the notes textarea.py-8padding and centered flex layout for easier target acquisition.i18n
en.json,de.json,pl.json):common.optional— "(Optional)" / "(Optional)" / "(Opcjonalne)"scan.gs1CoverageNote— regional coverage explanationsubmit.countryExplainer— country selection explanationsubmit.stepIndicator— step 2 of 2 indicatorDocs
docs/DESIGN_REFRESH_SPEC.md— Audit planning artifact documenting the broader design refresh spec.Test Coverage
New tests (7):
feedActive=true(videoreadyState=3,videoWidth=640,playingevent) before assertingsetTimeout(fn, 15_000)getAllByText("(Optional)")≥ 4)Updated tests (8 selectors):
getByLabelText("Brand")/getByLabelText("Category")/getByLabelText("Notes")updated to regex (/^Brand/,/^Category/,/^Notes/) to accommodate the new "(Optional)" suffix.Verification
Files Changed
10 files changed, +820 / -26 lines