# Customer portal files — local repair and remaining browser acceptance

The first live Maya upload returned HTTP 400 because the browser client forced
`Content-Type: application/json` onto a `FormData` request. The refreshed file
response was also treated as an array although the provider returns an object
with a `files` member. The service deleted files with its customer/path
arguments reversed, omitted the trailing slash from the customer prefix, and
uploaded customer documents through the provider's public-read default.

The local repair keeps the multipart boundary under the browser, normalizes the
`{ files, ... }` response, uses a trailing-slash customer namespace, validates
that a requested path belongs to the signed-in customer, creates short-lived
signed URLs, uploads privately without public thumbnails, checks deletion
results, and leaves a visible error when a reload cannot be confirmed. The
controller/service argument order is corrected without changing staff or guest
ticket routes.

Eight focused checks pass: identity and tenant validation, traversal and
prefix-collision rejection, list normalization and signed URL expiry, sibling
folder rejection, private upload, confirmed deletion, multipart boundary
handling, and the UI's failed-readback behavior. The base-app typecheck passes.
The source snapshots and test output are retained in
`assets/portal-files-fix/`.

The original live 400 is retained as diagnostic evidence. The repaired local
browser run now passes the customer half: Maya uploaded `room-brief.txt`, the
UI showed 83.0 B, and a full reload showed the same filename. Evidence is
retained in `../application-fixes/assets/reservation-display/maya-files-upload.png`.
The Cedar owner then read the exact folder through DAM → Document → File
Manager's recursive file listing and found the same private `room-brief.txt`
(83 bytes) under `client-account/maya.portal@cedarform.test/`. The sanitized
readback is in `assets/reservation-display/staff-dam-readback.json`.
