The detail endpoint's info inherits MediaCount from the board row DTO but
the service never sets it, so it serializes as 0. The previous guard trusted
any numeric value and returned that 0, leaving the slide-over completion gate
reporting "At least 1 photo" even after an upload. Take the larger of the
reported count and the stored (non-legacy) attachments so a positive API
count still wins while a spurious or omitted 0 no longer masks present media.
Dropping the unused mapped-row argument also removes a flat-payload path that
would have counted legacy items.
Late roster data no longer overwrites notes typed for the current selection.
The notes baseline is keyed by vendor, so a save never patches a previous
company. Changing the primary service clears leftover notes, and a save
with a missing baseline reads the current roster first. Also drops ticket
keys from shipped comments.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(locations): derive site areas from state
* chore(locations): keep ticket keys out of source comments
* feat(locations): manage ordered site contacts
---------
Co-authored-by: Codex Review Integration <codex-review@local.invalid>
The shared ky instance declared Content-Type: application/json for every
request. Ky bakes instance headers into the Request it builds, so a FormData
body left with that header, and the beforeRequest hook that deleted it ran
after the boundary had already been dropped: the multipart body went out
with no Content-Type at all. ASP.NET then cannot bind [FromForm] fields, so
the completion-doc upload (dialog and slide-over "Upload signed PDF")
returned 400 "The file field is required.", media category updates arrived
without category or workOrderVersion, and the legacy edit form post lost its
fields.
Drop the instance-wide Content-Type. Ky still sets application/json for
json bodies, and the runtime sets multipart/form-data with the boundary.
The old unit test tolerated a missing header, and ran under jsdom whose
FormData the Node Request cannot serialize. It now runs in the node
environment and requires a multipart Content-Type whose boundary matches the
body. A Pixel 7 viewport e2e covers the signed PDF upload and JPG/MP4/MOV
media upload plus categorize.
Add a help icon with hover/focus tooltip copy to the Schedule On, Due
Date, and Completed Date board table headers so dispatchers can see
what each date drives without opening a row (SH-195).
Replace the free-text vendor State with a closed 50-state list, add an
admin-only Service Area select below Notes, a Service Area advanced filter
(stable ids plus Unassigned) and a Service Area directory column.
The inline status control no longer offers the automatic Incomplete and
Scheduled states or the work order's current status. Inline create keeps its
explicit Incomplete/Pending/Scheduled creation list.
Audit Log entries now show date and time for every entry instead of a
time-only stamp for manual changes. Type and Severity entries read
'Changed Type from X to Y', a severity cleared by a type correction reads
'from SEV N to —', and the generic fallback no longer renders a raw
FieldChanged action name.
Generating or uploading the completion document now moves a closable work
order to Completed without the manual confirmation, as SH-115 requires. The
SH-114 closability gate is evaluated on the row the server returned after the
document save; Save draft, Not required, Past Due, Completed and Canceled rows
are never auto-completed. The follow-up status patch uses that row's
rowVersion so it does not conflict with the document save.
Board create failures toasted the backend message verbatim, exposing
field names such as siteCode and vendorId from the inline row and the
wizard. Map known codes to product copy and fall back to generic copy
for every other failure.
Wrap every inline creation cell in the bordered field box used by Site and
WO #, top-aligned like the prototype inline row. Show a persistent asterisk
on the empty WO # placeholder, the Site label in the create site dialog,
and the POC name and phone inputs in the create site dialog and wizard.