A duplicate work order number edit returns 422 DuplicateWoNumber from the
board PATCH endpoint, which mapped to the generic save-failed copy. Add the
code to the known validation copy map so edits reuse the same duplicate-WO
product text as create, and share the message constant across both paths.
Split DashboardPage's scope/paging state into useDashboardScope and the header
controls into DashboardHeaderActions, and flatten parseDashboardDrilldownParams'
hasAny check, keeping every function under the complexity/line-count gates.
- SH-336: add the dispatcher-scope picker (All / My WOs / individual) for
Scheduler/Admin via a new viewAllDispatchersOnDashboard role gate, a static
'My WOs' label for Dispatcher, and hide the dispatcher tables (and skip their
queries) when the viewer cannot see all dispatchers. Scope drives dispatcherId
on stats, workload, performance, and regions (SH-347 contract).
- SH-352: add the Status Distribution module with its empty state and status
drill-down.
- SH-347: show Assigned/Completed counts and colour the completion rate
(green >=90, amber 70-89, red <70) on Dispatcher Performance.
- SH-348: drill a region bar into the board region filter; hold the
unconfirmed 'Unmapped/Other' bucket off the chart.
- SH-349: label Vendor Insights as all-time, company-wide.
- Make the drilldown-filters effect fire only on URL change; add a deterministic
clock to the status-drilldown test; add the dashboard visual regression spec
and baselines.
- Gate the Revoke action on the approved queue row and the detail modal
to admin users; non-admins get a disabled button with a plain
'Only admins can revoke uplifts' tooltip and the dialog cannot open.
- Stop nesting an h2 inside DialogTitle in the uplift detail modal
(hydration error) by rendering the title text as a non-heading.
- Handle work-order detail/uplift query 403s quietly in the detail
modal: sections fall back to 'Unavailable' and the queries suppress
the global error toast (QueryCache now honors suppressErrorToast).
- Extract UpliftDecisionDialogs and openUpliftEvidence from the page to
meet the changed-file maintainability gate (complexity 23 -> pass).
computeIsPastDue and the day view's todayKey derived today from
toISOString() (UTC). West of UTC that rolls over hours early, so work
scheduled for today showed as Past Due every evening. Both now use the
local todayIso() helper the board already uses; regression tests pin
23:30 and 00:30 in America/Sao_Paulo with fake Date.
Test stability:
- adv-search page sync: scope pagination queries with within() instead of
scanning ~330 buttons / 3000 nodes on every waitFor poll
- pm-schedules-list-actions: static import of the edit page so module
transform does not count against the 5s test budget
- vendor create modal / detail drawer: userEvent.setup({ delay: null });
15s describe budget for full MUI dialog renders under parallel load
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.