chore: commit pending changes before custom services implementation

This commit is contained in:
2026-06-13 11:27:47 +01:00
parent 45814a4040
commit d77f330bda
4 changed files with 77 additions and 53 deletions
+1
View File
@@ -64,6 +64,7 @@
"@zxcvbn-ts/language-common": "^3.0.4", "@zxcvbn-ts/language-common": "^3.0.4",
"@zxcvbn-ts/language-en": "^3.0.2", "@zxcvbn-ts/language-en": "^3.0.2",
"cropperjs": "^1.6.2", "cropperjs": "^1.6.2",
"heic2any": "^0.0.4",
"maplibre-gl": "^5.24.0" "maplibre-gl": "^5.24.0"
} }
} }
@@ -4,6 +4,7 @@
import FileDropZone from '$lib/components/ui/file-drop-zone.svelte'; import FileDropZone from '$lib/components/ui/file-drop-zone.svelte';
import { authStore } from '$lib/stores/auth.svelte'; import { authStore } from '$lib/stores/auth.svelte';
import * as AlertDialog from '$lib/components/ui/alert-dialog'; import * as AlertDialog from '$lib/components/ui/alert-dialog';
import heic2any from 'heic2any';
// =============== Image Upload =============== // =============== Image Upload ===============
let uploading = $state(false); let uploading = $state(false);
@@ -28,9 +29,60 @@
let hasOversizedFiles = $derived(uploadFiles.some(isFileTooBig)); let hasOversizedFiles = $derived(uploadFiles.some(isFileTooBig));
function handleFilesDropped(files: File[]) { function isHeicFile(file: File): boolean {
uploadFiles = files; const name = file.name.toLowerCase();
generatePreviews(files); return name.endsWith('.heic') || name.endsWith('.heif');
}
/**
* Check if the browser can natively decode a HEIC file.
* Safari (macOS/iOS) and Chrome-on-Android have native HEIC support,
* which preserves HDR metadata and color profiles better than any conversion.
*/
function supportsNativeHeicDecode(file: File): Promise<boolean> {
return new Promise((resolve) => {
const img = new Image();
const url = URL.createObjectURL(file);
img.onload = () => { URL.revokeObjectURL(url); resolve(true); };
img.onerror = () => { URL.revokeObjectURL(url); resolve(false); };
img.src = url;
});
}
async function convertHeicToPng(file: File): Promise<File> {
const result = await heic2any({ blob: file, toType: 'image/png' });
const blob = Array.isArray(result) ? result[0] : result;
const pngName = file.name.replace(/\.(heic|heif)$/i, '.png');
return new File([blob], pngName, { type: 'image/png' });
}
async function handleFilesDropped(files: File[]) {
const converted = await Promise.all(
files.map(async (file) => {
if (!isHeicFile(file)) return file;
// Try native browser HEIC decode first (Safari, Chrome w/ HEVC)
// This preserves HDR metadata & color profiles at full quality
try {
const nativeOk = await supportsNativeHeicDecode(file);
if (nativeOk) return file;
} catch {
// Fall through to heic2any
}
// Fallback: convert to PNG via heic2any (libheif WASM)
// PNG is lossless, libheif handles ICC profiles correctly,
// and the downstream canvas pipeline is 8-bit anyway.
try {
return await convertHeicToPng(file);
} catch (e) {
console.error('HEIC conversion failed for', file.name, e);
return file;
}
})
);
uploadFiles = converted;
generatePreviews(converted);
} }
/** Generate scaled previews for each file */ /** Generate scaled previews for each file */
+4 -5
View File
@@ -13,12 +13,12 @@
"state": { "state": {
"type": "markdown", "type": "markdown",
"state": { "state": {
"file": "Crussell/Technical Manual.md", "file": "Crussell/Future Work - Gap Backlog.md",
"mode": "source", "mode": "source",
"source": false "source": false
}, },
"icon": "lucide-file", "icon": "lucide-file",
"title": "Technical Manual" "title": "Future Work - Gap Backlog"
} }
} }
] ]
@@ -78,8 +78,7 @@
} }
], ],
"direction": "horizontal", "direction": "horizontal",
"width": 300, "width": 300
"collapsed": true
}, },
"right": { "right": {
"id": "2750d7726f904ef3", "id": "2750d7726f904ef3",
@@ -172,10 +171,10 @@
}, },
"active": "6776d739ee18449c", "active": "6776d739ee18449c",
"lastOpenFiles": [ "lastOpenFiles": [
"Crussell/Technical Manual.md",
"Crussell/Future Work - Gap Backlog.md", "Crussell/Future Work - Gap Backlog.md",
"Crussell/Loyalty & Discount System Reference.md", "Crussell/Loyalty & Discount System Reference.md",
"Crussell/Overview.md", "Crussell/Overview.md",
"Crussell/Technical Manual.md",
"Crussell/User Manual.md", "Crussell/User Manual.md",
"Crussell/Admin Manual.md", "Crussell/Admin Manual.md",
"Crussell/Test Implementation Plan.md", "Crussell/Test Implementation Plan.md",
+17 -45
View File
@@ -38,23 +38,23 @@ These are blockers: missing functionality that prevents daily operations, legal
These improve the experience or add features, but the business can operate without them. These improve the experience or add features, but the business can operate without them.
| # | Gap | Effort | Area | Notes | | # | Gap | Effort | Area | Notes |
|---|---|---|---|---| | --- | ---------------------------------------- | -------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 12 | **One-off custom services** | M (1-2d) | Full-stack | Admin can't create single-use services. Every custom job (bridal party, special request) must be added to the permanent catalog. | | 12 | **One-off custom services** | M (1-2d) | Full-stack | Admin can't create single-use services. Every custom job (bridal party, special request) must be added to the permanent catalog. |
| 13 | **One-off exceptional hours** | M (1d) | Full-stack | Single-day overrides (dentist appointment, afternoon off) require creating a full exceptional group. Time blockers handle unavailable periods; one-off *open* hours (e.g., "open Sunday 2pm-5pm") still need simplification. | | 13 | **One-off exceptional hours** | M (1d) | Full-stack | Single-day overrides (dentist appointment, afternoon off) require creating a full exceptional group. Time blockers handle unavailable periods; one-off *open* hours (e.g., "open Sunday 2pm-5pm") still need simplification. |
| 14 | **Referral system UI** | M (1-2d) | Full-stack | Backend complete — registration validates codes, relationships recorded. Users can't see their referral code or track uses. Admin can't manage referral campaigns. | | 14 | **Referral system UI** | M (1-2d) | Full-stack | Backend complete — registration validates codes, relationships recorded. Users can't see their referral code or track uses. Admin can't manage referral campaigns. |
| 15 | **Analytics endpoints** | M (1-2d) | Backend | `handlers/admin/analytics.go` is 1 line. `get_monthly_business_summary()`, `get_sales_totals()` SQL functions exist but not wired. | | 15 | **Analytics endpoints** | M (1-2d) | Backend | `handlers/admin/analytics.go` is 1 line. `get_monthly_business_summary()`, `get_sales_totals()` SQL functions exist but not wired. |
| 16 | **API documentation** | M (1-2d) | Backend | No OpenAPI/Swagger spec. No generated docs. New developers must read code. | | 16 | **API documentation** | M (1-2d) | Backend | No OpenAPI/Swagger spec. No generated docs. New developers must read code. |
| 17 | **Per-user rate limiting** | M (1d) | Backend | Rate limiter is IP-based. Authenticated users could abuse from multiple IPs. Should track by user ID + IP. Pure Go — no Redis needed for single-instance. | | 17 | **Per-user rate limiting** | M (1d) | Backend | Rate limiter is IP-based. Authenticated users could abuse from multiple IPs. Should track by user ID + IP. Pure Go — no Redis needed for single-instance. |
| 18 | **Begin button (Today page)** | S (2-3h) | Full-stack | Manual start for early arrivals. Currently auto-inferred only. Gray out if >3 hours away. | | 18 | **Begin button (Today page)** | S (2-3h) | Full-stack | Manual start for early arrivals. Currently auto-inferred only. Gray out if >3 hours away. |
| 19 | **Booking conflict detection for users** | S (2-3h) | Backend | Users can theoretically double-book themselves in two tabs. Reservation system helps but doesn't fully prevent. | | 19 | **Booking conflict detection for users** | S (2-3h) | Backend | Users can theoretically double-book themselves in two tabs. Reservation system helps but doesn't fully prevent. |
| 20 | **Service category/tag management** | M (1-2d) | Full-stack | Services have no category field. Admin scrolls through a flat list. No way to group (manicure vs pedicure vs nail art). | | 20 | **Service category/tag management** | M (1-2d) | Full-stack | Services have no category field. Admin scrolls through a flat list. No way to group (manicure vs pedicure vs nail art). |
| 21 | **No-show tracking dashboard** | S (2-3h) | Frontend | `forgiven_no_shows` table exists but no UI. Admin can't see which users have accumulated no-shows. | | 21 | **No-show tracking dashboard** | S (2-3h) | Frontend | `forgiven_no_shows` table exists but no UI. Admin can't see which users have accumulated no-shows. |
| 22 | **Dark mode** | M (1-2d) | Frontend | SvelteKit + Tailwind supports it. No toggle or `prefers-color-scheme` support. | | 22 | **Dark mode** | M (1-2d) | Frontend | SvelteKit + Tailwind supports it. No toggle or `prefers-color-scheme` support. |
| 23 | **PWA support** | L (3-5d) | Frontend | No service worker, no manifest.json, no offline support. Customers can't "install" the app. | | 23 | **PWA support** | L (3-5d) | Frontend | No service worker, no manifest.json, no offline support. Customers can't "install" the app. |
| 24 | **Recurring bookings** | L (3-5d) | Full-stack | Customers can't book the same slot weekly/monthly. Would need `recurring_bookings` table + background job. | | 24 | **Recurring bookings** | L (3-5d) | Full-stack | Customers can't book the same slot weekly/monthly. Would need `recurring_bookings` table + background job. |
| 25 | **Idempotency key cleanup** | S (1h) | Backend | `idempotency_key` columns added to `bookings`, `payments`, `till_sales` with unique constraints. No retention policy — keys accumulate indefinitely. | | 25 | **Idempotency key cleanup** | S (1h) | Backend | `idempotency_key` columns added to `bookings`, `payments`, `till_sales` with unique constraints. No retention policy — keys accumulate indefinitely. |
| 26 | **Gift card self-service portal** | M (1d) | Frontend | Users can see gift card balance on Account page but can't independently redeem to balance without admin. | | 26 | **Gift card self-service portal** | M (1d) | Frontend | Users can see gift card balance on Account page but can't independently redeem to balance without admin. |
### External Stretch (Requires Third-Party Access) ### External Stretch (Requires Third-Party Access)
@@ -78,31 +78,3 @@ E10 Sentry → error tracking, 5xx alerting
``` ```
All local (MVP + Stretch) items have zero external dependencies. All local (MVP + Stretch) items have zero external dependencies.
---
## Notes on Removed Items
The following items were completed and removed from this document:
- Delete account endpoint, GDPR data export, delete_guest_user SQL function, GDPR anonymization
- Walk-in guest booking, walk-in slot blocking, reservation transitions
- ApprovalModal decline/cancel, admin notification panel, admin schedule page
- BookingFlow welcome step, shared format utilities, patch_test_duration_hours
- created_by_name on bookings, admin login redirect, customer relationship view
- Admin UI for edit requests, booking rescheduling (user + admin)
- Auto lunch protection, health check endpoint, graceful shutdown
- HSTS + Referrer-Policy headers, console.log cleanup, alert prototypes
- Idempotency keys for bookings, loyalty stamp redemption + discount system
- Test DB optimization, image optimization, image deletion fix
- User notification preferences UI, payment integration (Square), deposit payments
- Tips UI, gift card system (backend + admin UI), referral code registration
- CharCounter, loginInProgress rate limiting, profile picture limits, formatDateISO
- Portfolio upload limits, JWT revocation, MapLibre GL, NavBar enhancements
- TodayCalendar improvements, Svelte 5 $derived, SvelteDate, $app/paths resolve
- Vite WASM support, portfolio multi-format images, loyalty stamp redesign
- Admin role restrictions, account restrictions, BookingsByCreatedRange, enriched edit requests
- Email check endpoint, PhoneInput, business settings API, gift card audit log
- Gift card expiry, idle account cleanup, inventory cards, expired balance recovery
- Till sale idempotency, VAT on till sales, test infrastructure upgrades
- Financial data retention, gift card transaction audit log