A wave of internal improvements: security, performance, resilience
This week was more about paying down debt than shipping new things, but the result is that the app feels more solid. Here is a summary so you know where things got better even if your eye did not catch it.
Security
- JSON-LD escaping on the public menu to block injections from exotic dish names.
- Validation of external URLs before showing them on public pages.
- Image upload now rejects SVGs and caps size at 5 MB. Previously you could sneak in a scripted SVG.
- Public edit token signed with HMAC: nobody can edit someone else's reservation, even if they guess the ID.
- Idempotent Stripe webhook: if the same event arrives twice (Stripe sometimes retries), it only applies once.
- Rate limiting and access guards on six Gemini functions to prevent cost abuse.
- Roles:
workspace-member-upsertno longer allows escalation toownerfrom a lower role.
Performance
- Admin overview stopped doing N+1 when listing restaurants (it was one query each; now a single one).
- Sentry lazy-loaded: only loads when needed, not on every visit to the public home.
date-fnsin small bundle instead of the full package, saving about 80KB per load.
Resilience
- Frontend auto-reload if a chunk preload fails after a deploy: the user does not see a broken screen, it reloads on its own.
- Snapshot of
dist/plus a quick rollback script to revert to the previous version if a deploy goes sideways. - Reminders now respect the restaurant's time zone (they used to send in UTC and sometimes ran an hour early).
- Public stats cron rescheduled so it does not collide with backups.
If something felt slow or odd and you notice it no longer does, this was it.