feat(web): speculative pre-decode on a valid VIN (faster counter lookups)
The moment the VIN reaches 17 valid chars, warm the full decode in the background (350ms debounce, deduped per VIN, fire-and-forget) so the "Şase Çöz" click hits the backend lock / 24h positive cache and returns near-instantly. If the warm is still in flight when the dealer clicks, the button's decode lock-waits on it and navigates the moment it lands. No analytics/state/navigation from the pre-decode — vin_decoded and the success/candidates/error events still fire only from the button path, so the CRO funnel stays = real intent. Raw fetch + AbortController (api.post takes no signal), mirroring the existing 17-char preview effect. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,7 @@ function SearchPage() {
|
||||
const candidatesShownAtRef = useRef<number | null>(null);
|
||||
const lastAttemptedVinRef = useRef<string | null>(null);
|
||||
const attemptCountRef = useRef<number>(0);
|
||||
const preDecodedVinRef = useRef<string | null>(null);
|
||||
const [vin, setVin] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -147,6 +148,36 @@ function SearchPage() {
|
||||
return () => controller.abort();
|
||||
}, [vin]);
|
||||
|
||||
// ─── Speculative pre-decode (the moment the VIN is valid) ──────────────────
|
||||
// Warm the full decode in the background before the user clicks "Şase Çöz", so
|
||||
// the button-click decode hits the backend lock / 24h positive cache and returns
|
||||
// near-instantly — the dealer at the counter loses no time. Fire-and-forget: no
|
||||
// state, no analytics, no navigation (the button path owns all of that). Deduped
|
||||
// per VIN, debounced + aborted so typing/paste-then-edit doesn't spam decodes.
|
||||
useEffect(() => {
|
||||
const cleanVin = vin.toUpperCase().trim();
|
||||
if (!isValidVin(cleanVin) || preDecodedVinRef.current === cleanVin) return;
|
||||
|
||||
const controller = new AbortController();
|
||||
const timer = window.setTimeout(() => {
|
||||
preDecodedVinRef.current = cleanVin;
|
||||
// Warm the backend decode chain; the result is intentionally discarded.
|
||||
fetch("/api/vehicles/decode", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ vin: cleanVin }),
|
||||
signal: controller.signal,
|
||||
}).catch(() => {
|
||||
/* speculative warm — ignore result/errors; the button-click decode owns the outcome */
|
||||
});
|
||||
}, 350);
|
||||
|
||||
return () => {
|
||||
window.clearTimeout(timer);
|
||||
controller.abort();
|
||||
};
|
||||
}, [vin]);
|
||||
|
||||
// ─── Auto-focus ────────────────────────────────────────────────────────────
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
|
||||
Reference in New Issue
Block a user