fix(uat-batch-1): wave-1 blocker bugs — supplemental gate, file FK, downloads, search dedup, notes stale, expense form, vocab
Surgical fixes for the 7 UAT blockers that prevent productive forward
testing. Each item has a corresponding entry in alpha-uat-master.md.
- supplemental-info route relocated out of (portal) so it bypasses the
isPortalDisabledGlobally() kill-switch. URL unchanged.
- file upload service derives client_id/company_id/yacht_id from
(entityType, entityId) when not explicitly passed, so interest-tab
uploads no longer land with client_id=NULL and stay visible in the
Attachments list.
- triggerBlobDownload / triggerUrlDownload helpers in src/lib/utils
attach the anchor to the DOM before click so Chromium honours the
download attribute; 7 sites refactored, file-named downloads stop
arriving as bare UUIDs.
- search-nav-catalog dedupes by href at the result-collection layer so
the same href can no longer surface twice in the command-K dropdown
(kills the React duplicate-key warning); /admin/templates entries
merged into a single richer-keyword variant.
- NotesList gains a parentInvalidateKey prop, wired through all five
callers (interest, client, yacht, company, residential client/
interest) so the Overview "Latest note" teaser refreshes when a note
is added in the Notes tab.
- expense-form-dialog: setValue('receiptFileIds') / setValue(
'noReceiptAcknowledged') on upload/clear/checkbox so the schema-level
refine sees the field and Create stops silently no-op'ing on submit.
- bulk-add-berths-wizard: side-pontoon dropdown now reads through
useVocabulary('berth_side_pontoon_options') instead of a wrong local
enum ('Port', 'Starboard', 'Bow', 'Stern') — wizard data now matches
the rest of the platform + honours admin-editable per-port overrides.
tsc clean. 1419/1419 vitest. lint clean on touched files.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
37
src/lib/utils/download.ts
Normal file
37
src/lib/utils/download.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Trigger a browser download for a Blob with the given filename.
|
||||
*
|
||||
* The naive pattern (`<a download={name}>` synthesized, click()'d, then
|
||||
* URL.revokeObjectURL'd) silently drops the `download` attribute on
|
||||
* Chromium-based browsers when the anchor isn't attached to the DOM.
|
||||
* The browser then names the file after the blob URL (a UUID) with no
|
||||
* extension. Appending to body, clicking, removing — in that order —
|
||||
* keeps the attribute honoured everywhere.
|
||||
*
|
||||
* `URL.revokeObjectURL` runs on the next tick so the browser has a
|
||||
* chance to read the URL before it's released; revoking synchronously
|
||||
* can race with the download start in Safari.
|
||||
*/
|
||||
export function triggerBlobDownload(blob: Blob, filename: string): void {
|
||||
const url = URL.createObjectURL(blob);
|
||||
triggerUrlDownload(url, filename);
|
||||
// Defer revoke to next microtask so the navigation can latch the URL.
|
||||
queueMicrotask(() => URL.revokeObjectURL(url));
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a browser download from an already-resolved URL (e.g. a
|
||||
* presigned S3 / MinIO URL). Same DOM-attached pattern as
|
||||
* triggerBlobDownload — without it Chromium drops the `download`
|
||||
* attribute and the file lands with the URL's last path segment as
|
||||
* the filename (a UUID, no extension).
|
||||
*/
|
||||
export function triggerUrlDownload(url: string, filename: string): void {
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
a.rel = 'noopener';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
}
|
||||
Reference in New Issue
Block a user