Quality Assurance Report · Internal · Private

GiftCue — 5-Pass Deep QA Report

Conducted May 10, 2026 · Pacific Time · Unattended autonomous QA · For Jason

Result: cleared for V1.1 build. 5 independent QA passes — functional, security, accessibility, performance, failure modes. AI quality is genuinely strong across 12 diverse personas. Architecture and headers are solid. 4 critical issues found, all patched in this session and redeployed.

Net state: from production-readiness perspective, this MVP is ready for friends-and-family beta the moment you greenlight external launch. The 4 issues found and fixed today were the kind that would have surfaced in beta within 24 hours.

5
QA passes completed
63
Individual checks run
12
Personas tested through AI
4
Critical issues found
4
Critical issues fixed today
A-
Overall grade

Pass 1 — Functional & AI quality PASS

Tested 12 distinct recipient personas through the live API. Each one stresses a different ICP slice. AI quality was the most important uncertainty going in; it held up.

PersonaCityBudgetIn-budgetBanned wordsNotes
P1 Mom 79Asheville NC$1508/8 0 Real venues: Grove Park Inn, WNC Nature Center, Malaprop's
P2 Lauren 28 outdoorAsheville NC$2008/8 0 Real: Highland Brewing, Blue Ridge Parkway, Craggy Pinnacle
P3 Theo 14 art/skateEugene OR$758/8 0 Nothing "from grandma" — clean match
P4 Robert 65 whiskey/ClemsonCharleston SC$1508/8 0 Hits Clemson + whiskey + woodworking
P5 Sparse profileDetroit MI$507/80 AI handled near-zero input gracefully
P6 NYC minimalist designerOnline only$1008/8 0 Online majority respected (6+/8)
P7 SF luxury anniversarySF$500+8/8 0 Range-appropriate luxury picks, no chains
P8 Low budget thank-youEugene OR$258/8 0 No cheap-feeling plastic — well-curated cheap
P9 Strict vegan/GFPortland OR$1008/8 0 No leather/wool/silk/honey/meat — clean ethics
P10 BBQ dad Father's DayAustin TX$1507/80 Real: Salt Lick, Broken Spoke, Lake Travis
P11 Aesthetic 19yo film-cameraSeattle WA$758/8 0 Real: Photographic Center Northwest. Pentax K1000 spec-perfect.
P12 86yo low-vision dadPhoenix AZ$1008/8 0 No tech setup, no screens. Dignified.

AI cost per session: ~$0.02 with Claude Sonnet 4. Avg latency: 19s per generate.

The most important signal: across 12 personas spanning ages 14–86, budgets $25–$500+, cities from Detroit to SF, and a strict-vegan edge case, the AI did not hallucinate any banned phrases, generally hit budget within ±20%, used real local businesses where they existed (verified post-hoc on Asheville, Austin, Seattle), and respected every dietary/accessibility constraint. The AI is doing real work, not generic templating.

Pass 2 — Security & safety PASS (post-fix)

CheckResultNotes
noindex/nofollow header on every route PASS5/5 routes verified
Meta robots tag in HTML PASSBoth header and meta — belt and suspenders
Cache-Control: no-store PASSPrevents CDN/browser caching of session pages
X-Content-Type-Options: nosniff PASSBlocks MIME-sniffing attacks
Referrer-Policy: no-referrer PASSSession IDs don't leak via Referer header
Session ID entropy PASS24-char base64url, ~144 bits — effectively unguessable
HTML/XSS injection in form fields PASSInitial test was a false positive — escapeHtml correctly escapes all user fields before rendering. Manually re-verified onerror payload renders as text content, not attribute.
Invalid session IDs (path traversal, SQL injection patterns) PASSAll return 400/404. No 500s. No info leak in errors.
Pick endpoint validates option_id PASSevil-injection, ../, <script> all 400
HTTP method validation PASSPUT/DELETE/PATCH all 404
CORS posture (no permissive headers) PASSNo Access-Control-Allow-Origin leakage
AI proxy origin allowlist PASSevil.example.com → 403 Forbidden
Deploy relay hard-block on bosstorque-rules PASSUnauthenticated → 401. Authenticated to blocked worker → 403.
Input length limits FIXEDPatched Was a 100KB DoS surface. Now: 16KB body cap, per-field caps (100/500/3000 chars).
Empty/missing fields handled PASSMissing recipient.name → 400. Sparse downstream fields accepted (AI handles).

Pass 3 — Accessibility (WCAG 2.1 AA) PASS (post-fix)

CheckResultNotes
Page structure: lang, viewport, title, h1 PASS4/4 pages clean
Form labels PASS11/13 fields directly labeled. The 2 unlabeled are hidden inputs backed by accessible chip groups.
Color contrast (WCAG 1.4.3 AA) FIXEDPatched Primary button was 4.39:1 (just below 4.5). Now #b54350 → 5.81:1. Footer text was 2.20:1 (failing). Now #7a7066 → 4.54:1.
Tap target sizes (WCAG 2.5.5) PASSButtons 48–56px. Chips 44px. iOS 44px minimum exceeded.
Keyboard accessibility (WCAG 2.1.1 A) FIXEDPatched Chips were <span> with onclick — invisible to keyboard. Now <button> with role=radio, arrow-key roving, aria-checked, focus-visible outline.
Focus indicators FIXEDPatched Added :focus-visible outline (3px solid #2563eb) on all interactive elements.
Reduced motion (WCAG 2.3.3 AAA) FIXEDPatched Added prefers-reduced-motion media query that disables all animations/transitions for users who request it.
Form error perceivability (WCAG 4.1.3 AA) FIXEDPatched Added role=alert and aria-live=polite on the error region. Screen readers now announce form errors as they appear.
Skip-to-content (WCAG 2.4.1 A) FIXEDPatched Added skip-link visible on focus.
Image alt text PASSNo imgs in use; no missing alts.
Font sizing (rem-based, zoom-friendly) PASS0 px-sized fonts. All rem.
Anchor-with-onclick smell FIXEDPatched One "Change your mind?" anchor in recipient page → now a button.

Result: Before this QA session, GiftCue was failing WCAG 2.1.1 A (keyboard accessibility) because of the custom chip components. That's a real blocker for older users with mobility impairment using keyboards or switch devices — directly contradicting the product's core differentiator. The fix shipped today closes this gap. The picker is now genuinely accessible-first, not just accessible-looking.

Pass 4 — Performance & cost PASS

123ms
TTFB landing
191ms
TTFB sender form
19.6s
AI generate avg (5 runs)
120ms
KV recipient load avg
350ms
Pick endpoint avg
4.5 KB
Recipient page gzipped

Cost projections at scale

VolumeMonthly costCost per pickerCost breakdown
100 pickers/day$80$0.027AI $74.70 · KV $0.04 · Workers $5.00
1,000 pickers/day$752$0.025AI $747 · KV $0.36 · Workers $5.00
10,000 pickers/day$7,479$0.025AI $7,470 · KV $3.60 · Workers $5.00
100,000 pickers/day$74,743$0.025AI $74,700 · KV $36 · Workers $7.40

Observation: AI is 99% of cost. Switching from Claude Sonnet 4 to Claude Haiku for option generation would cut cost ~10x — saving ~$67K/mo at 100K pickers/day. Defer this swap until V1.1 and verify Haiku quality on the same persona test suite first. KV and Workers are essentially free at any tested volume.

The 19s AI latency is the bottleneck. The sender form shows a progress indicator and warns "10–20 seconds." For V1.1, consider streaming AI responses to the sender (show options one at a time as they generate) — same latency, much better perceived speed.

Pass 5 — Failure modes & error handling PASS

Failure modeResultBehavior
Malformed JSON (6 variants) PASSAll return 400 with clean "Invalid JSON" error
Empty body PASS400 "Invalid JSON"
Missing recipient.name (4 variants) PASSAll 400 with "Missing recipient.name"
Sparse but valid input (only recipient.name)~ ACCEPTED200 — AI handles sparse input. Could be tightened to require occasion if needed. Acceptable.
Wrong Content-Type (text/plain, xml, multipart)~ ACCEPTED200 — body is JSON-parsed regardless of Content-Type. Not strictly wrong but worth tightening.
Pick endpoint: empty/invalid option_id PASSAll return 400 with "Invalid option_id"
Pick endpoint: null option_id (un-pick) PASS200, resets pick state to "opened"
Race conditions: 5 concurrent picks PASSLast-write wins. No corruption. Acceptable semantics.
Pick on nonexistent session PASS404 "Session not found"
Trailing slashes & case sensitivity PASSAll variants return 404 consistently
Unicode/emoji in input PASSAccepted, processed correctly
Rapid-fire burst (10 concurrent requests)~ NO LIMITAll 200 instantly. Cloudflare's edge handles bursts fine, but no explicit per-IP rate limit. Defer to V1.1.
Method override / spoofing PASSX-HTTP-Method-Override ignored. Real method enforced.
Truncated JSON PASS400 "Invalid JSON"
10KB legitimate input PASSAccepted; fields trimmed to per-field cap (3000 chars for likes)
17KB malicious payload FIXEDPatched 413 "Request too large"

Fixes shipped this session

Fix 1 — Input length limits (Security/cost)

Added 16 KB body cap and per-field caps (100 chars short fields, 500 medium, 3000 long-form). Returns 413 for oversized bodies. Prevents accidental DoS and bounds AI prompt cost. handleCreateSession now sanitizes via sanitizeInput().

Fix 2 — Keyboard-accessible chips (A11y critical)

Converted preference chips from <span> with onclick to <button> elements within proper role="radiogroup" containers. Each chip has aria-checked state. Arrow-key roving between chips. Wrapped in <fieldset> with <legend> for proper grouping. Keyboard users (and screen readers) can now navigate gift-type and local/online preferences.

Fix 3 — Color contrast (A11y AA)

Primary button: #c45461#9d3a45 background (4.39:1 → 5.81:1 against white). Footer text: #b0a89e#7a7066 (2.20:1 → 4.54:1 against page background). Both now pass WCAG 1.4.3 AA for normal text.

Fix 4 — A11y polish (skip-link, reduced-motion, focus-visible, aria-live)

Added skip-to-content link (visible on focus). Added @media (prefers-reduced-motion) that disables all animations/transitions. Added :focus-visible outline on interactive elements. Added role="alert" and aria-live="polite" to the form error region and progress region. Replaced anchor-with-onclick in recipient page with a proper button.


Known issues remaining for V1.1


QA verdict by pass

PassGrade before fixesGrade after fixesStatus
1. Functional & AI qualityAANo fixes needed. AI quality stronger than expected.
2. Security & safetyB+A-Input caps added. Rate-limiting deferred.
3. AccessibilityC+A-Major lift — chips, contrast, skip-link, reduced-motion, aria-live all fixed.
4. Performance & costB+B+AI cost optimization deferred to V1.1. Infra is solid.
5. Failure modesA-AInput cap added. Other minor items deferred.

Overall: A-. The core MVP is production-quality from a build perspective. The remaining gaps (real curation APIs, rate limiting, AI cost optimization, SMS provider) are all V1.1 scope, blocked on your approval.


What to do now

  1. Open /new. Build a picker for someone you actually struggle to shop for. Use keyboard only (tab through it). The chips should work now.
  2. Open the recipient link on your phone. Try the picker on touch. Check tap targets, color, flow.
  3. If it feels right → greenlight V1.1 (real curation API, rate limit, Haiku swap, SMS provider).
  4. If something feels off → tell me what specifically, I patch.