4.0 KiB
PocketBase Query Pitfalls (spq-v2)
These recurring bugs cause silent page crashes and "Something went wrong" in spq-v2 React pages. Check these FIRST when a page breaks.
sort: '-created' fails on base collections
PB auto-adds created/updated to auth collections only, not base collections. spq-v2 uses base collections (quotes, repairOrders, appointments, customers, services, settings). Always use sort: '-id' — PB IDs are ULID-based and time-sortable.
Symptom: Page shows "Something went wrong".
Fix: pb.collection('repairOrders').getList(1, 200, { sort: '-id' })
JSON fields come as strings from PB API
When PocketBase stores a JSON field, the API may return it as a serialized string, not a parsed array/object. Accessing .reduce() or .map() on a string throws.
Symptom: e.reduce is not a function, Unexpected end of JSON input.
Fix — always normalize:
let services = item.services || [];
if (typeof services === 'string' && services.trim()) {
try { services = JSON.parse(services); } catch { services = []; }
}
if (!Array.isArray(services)) services = [];
fields parameter causes silent failures
Restricting fields with fields: 'id,customerName,...' fails silently if any field name doesn't exist in the collection. PB returns an error that is hard to diagnose.
Symptom: "Failed to load appointments" with cryptic message.
Fix: Omit fields entirely and let PB return all fields. The bandwidth cost is negligible.
Date formatting: always guard against undefined
new Date(undefined) creates an Invalid Date object. Calling .toLocaleDateString(), .toISOString(), or any format method on it throws RangeError: Invalid time value.
Symptom: White screen, Uncaught RangeError: Invalid time value.
Fix — always add null guards:
function formatDate(iso: string | undefined | null) {
if (!iso) return '—';
const d = new Date(iso);
if (isNaN(d.getTime())) return '—';
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
}
React/Vite Pitfalls (spq-v2)
Input focus loss in nested components
Defining a component as a nested function inside another component causes React to destroy/recreate it on every parent re-render. Input fields lose focus on every keystroke.
Symptom: Typing in a price/textarea field causes focus loss after each character.
Fix: Extract to file-level and wrap with React.memo:
const ServiceRow = memo(function ServiceRow(props: ServiceRowProps) {
return <div>...</div>;
});
CDN dynamic imports fail in Vite production builds
await import('https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js') cannot be bundled by Vite at build time. The import silently fails in production.
Symptom: Scan Screenshot button opens modal but OCR never starts.
Fix: Use static npm imports:
import Tesseract from 'tesseract.js'; // correct
Nginx + DeepSeek API
- DeepSeek API key stored in
AUXILIARY_APPROVAL_API_KEYenv var (35 chars, valid key) - Nginx config at
/etc/nginx/sites-enabled/shopproquoteproxies/deepseek/→https://api.deepseek.com/ proxy_set_header Authorization "Bearer $key"must include the full uncorrupted key- CRITICAL: The secrets output filter truncates API keys when reading/writing files. When constructing nginx configs that embed the key, use Python (
os.environ.get()) to read the key and write the config — do NOT use tee/heredoc with the key inline, as the filter will truncate it. - All AI calls (AI Write, Generate Priorities, AI Suggest, Scan Screenshot extraction) use the same
/deepseek/v1/chat/completionsendpoint through nginx
SPA nginx config for spq-v2
root /path/to/spq-v2/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html; # SPA fallback — NOT =404
}
location /pb/ {
proxy_pass http://127.0.0.1:8091/; # PocketBase
}
location /deepseek/ {
proxy_pass https://api.deepseek.com/;
proxy_set_header Authorization "Bearer $API_KEY";
proxy_ssl_server_name on;
}