Remove Quick Login + scanner a11y polish (#56)

Drop alice/bob password prefill from the login page, add a regression
test, and improve bulk-toolbar and disambiguation accessible names.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
varutasu 2026-05-29 22:58:41 -05:00 committed by GitHub
parent c197dc61ed
commit e0218e4b05
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 85 additions and 59 deletions

View file

@ -0,0 +1,26 @@
---
name: purge-quick-login-from-loginpage
classification: fix
success_metric: |
pages/login.js no longer ships alice123/bob123 in client HTML; smoke sign-in
CTA unchanged.
status: closed
created: 2026-05-26
closed: 2026-05-29
pr: pending
---
# Convoy: purge-quick-login-from-loginpage
Remove production Quick Login buttons that exposed test-user passwords in
view-source HTML.
## Scope
- Delete Quick Login section + `handleQuickLogin` from `pages/login.js`
- Regression test: no quick-login copy or password literals in rendered output
## Out of scope
- Rotating alice/bob DB hashes (operators rotate manually if needed)
- `TESTING_GUIDE.md` dev account table (local QA reference only)

View file

@ -7,24 +7,22 @@ success_metric: |
uses list semantics and live count updates.
depends_on:
- redesign-scanner-flow
status: open
status: closed
created: 2026-05-27
closed: 2026-05-29
pr: pending
---
# Convoy: scanner-redesign-a11y-fixes
Closes P1 follow-up from `audit-redesign-scanner-flow-44`.
## Scope
- `components/ScannedCardItem.js` — ownership `role="status"`, labeled selects/buttons
- `components/CameraScanner.js` — camera control labels, disambiguation focus trap, video label, heading fix
- `pages/scanner.js` — create-collection modal a11y, bulk toolbar labels, queue list semantics, live count
- `lib/use-focus-trap.js` (new) — shared modal focus trap
**Note:** Core a11y shipped in PR #45; this follow-up closes remaining polish
(bulk toolbar semantics, disambiguation button labels, condition select ids).
## Acceptance criteria
1. Ownership badge announced by screen readers.
2. All icon-only and placeholder `<select>` controls have accessible names.
3. Disambiguation + create-collection modals trap focus and restore on close.
4. Scanned cards queue uses list semantics; count updates are polite live region.
- [x] Ownership badge announced by screen readers (PR #45 + tests)
- [x] Icon-only and placeholder controls labeled
- [x] Disambiguation + create-list modals trap focus (`use-focus-trap.js`)
- [x] Scanned cards queue uses list semantics; count updates are polite live region

View file

@ -1052,11 +1052,12 @@ export default function CameraScanner({ onCardScanned, onError }) {
<div className="space-y-2">
{disambiguation.candidates.map((candidate) => (
<button
key={candidate.id}
type="button"
key={candidate.id}
onClick={() => handleDisambiguationPick(candidate)}
className="w-full flex items-center gap-3 p-3 rounded-lg border text-left hover:opacity-90"
style={{ borderColor: 'var(--border)', backgroundColor: 'var(--bg-tertiary)' }}
aria-label={`Select ${candidate.name}${candidate.set_name ? `, ${candidate.set_name}` : ''}`}
>
{candidate.image_url ? (
<img src={candidate.image_url} alt="" className="w-12 h-16 object-cover rounded" />

View file

@ -160,9 +160,14 @@ export default function ScannedCardItem({
{!card.processed && (
<div className="flex flex-wrap items-center gap-3 mb-3">
<label className="flex items-center gap-2 text-sm" style={{ color: 'var(--text-secondary)' }}>
<label
htmlFor={`scan-condition-${card.id}`}
className="flex items-center gap-2 text-sm"
style={{ color: 'var(--text-secondary)' }}
>
<span>Condition</span>
<select
id={`scan-condition-${card.id}`}
value={card.condition || 'NM'}
onChange={(e) => onUpdateMetadata({ condition: e.target.value })}
className="px-2 py-1 rounded border text-sm"
@ -171,6 +176,7 @@ export default function ScannedCardItem({
borderColor: 'var(--border)',
color: 'var(--text-primary)',
}}
aria-label={`Condition for ${card.name}`}
>
{CONDITION_OPTIONS.map((option) => (
<option key={option} value={option}>

View file

@ -60,10 +60,6 @@ export default function Login() {
}
};
const handleQuickLogin = (email, password) => {
setFormData({ email, password });
};
return (
<AuthLayout>
<div className="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
@ -161,41 +157,6 @@ export default function Login() {
</button>
</div>
{/* Quick Login for Testing */}
<div className="mt-6 pt-6 border-t border-opacity-20" style={{ borderColor: 'var(--border)' }}>
<div className="text-center">
<p className="text-sm mb-3" style={{ color: 'var(--text-secondary)' }}>
Quick Login for Testing:
</p>
<div className="grid grid-cols-2 gap-3">
<button
type="button"
onClick={() => handleQuickLogin('alice@deckhearth.com', 'alice123')}
className="px-4 py-2 rounded-lg text-sm font-medium border border-opacity-20 transition-all duration-200 hover:shadow-md backdrop-blur-sm hover:bg-opacity-80"
style={{
backgroundColor: 'rgba(var(--bg-primary-rgb), 0.6)',
borderColor: 'var(--border)',
color: 'var(--text-primary)'
}}
>
👤 Alice
</button>
<button
type="button"
onClick={() => handleQuickLogin('bob@deckhearth.com', 'bob123')}
className="px-4 py-2 rounded-lg text-sm font-medium border border-opacity-20 transition-all duration-200 hover:shadow-md backdrop-blur-sm hover:bg-opacity-80"
style={{
backgroundColor: 'rgba(var(--bg-primary-rgb), 0.6)',
borderColor: 'var(--border)',
color: 'var(--text-primary)'
}}
>
👤 Bob
</button>
</div>
</div>
</div>
<div className="text-center">
<p className="text-sm" style={{ color: 'var(--text-secondary)' }}>
Don't have an account?{' '}

View file

@ -613,6 +613,7 @@ export default function Scanner() {
borderColor: 'var(--border)',
color: 'var(--text-secondary)'
}}
aria-label="Clear all scanned cards from queue"
>
Clear All
</button>
@ -659,15 +660,19 @@ export default function Scanner() {
{/* Floating Bulk Actions Toolbar */}
{selectedCards.size > 0 && (
<div className="fixed bottom-6 left-1/2 transform -translate-x-1/2 z-50">
<div className="rounded-2xl shadow-2xl border px-6 py-4 flex items-center gap-4 max-w-4xl"
style={{
backgroundColor: 'var(--bg-secondary)',
borderColor: 'var(--border)',
backdropFilter: 'blur(10px)'
}}>
<div
className="rounded-2xl shadow-2xl border px-6 py-4 flex items-center gap-4 max-w-4xl"
role="toolbar"
aria-label="Bulk actions for selected scanned cards"
style={{
backgroundColor: 'var(--bg-secondary)',
borderColor: 'var(--border)',
backdropFilter: 'blur(10px)',
}}
>
{/* Selection Count */}
<div className="flex items-center gap-2">
<div className="flex items-center gap-2" aria-live="polite" aria-atomic="true">
<div className="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold text-white"
style={{ backgroundColor: 'var(--accent-ember)' }}>
{selectedCards.size}

29
test/pages/login.test.js Normal file
View file

@ -0,0 +1,29 @@
// @vitest-environment jsdom
import { afterEach, describe, expect, it, vi } from 'vitest';
import { cleanup, render, screen } from '@testing-library/react';
import Login from '../../pages/login.js';
vi.mock('next/router', () => ({
useRouter: () => ({ push: vi.fn() }),
}));
vi.mock('../../components/AuthLayout', () => ({
default: ({ children }) => <div>{children}</div>,
}));
vi.mock('../../components/AnimatedFireLogo', () => ({
default: () => <div aria-hidden="true">logo</div>,
}));
describe('Login page', () => {
afterEach(() => cleanup());
it('renders the sign-in CTA without dev quick-login credentials', () => {
render(<Login />);
expect(screen.getByRole('button', { name: /sign in to deck hearth/i })).toBeTruthy();
expect(screen.queryByText(/quick login/i)).toBeNull();
expect(document.body.textContent).not.toMatch(/alice123|bob123/);
});
});