Comment and mention items open the work order on the Comments tab with the
comments they were raised for highlighted; a grouped assignment opens the
dispatcher's own open queue; uplift decisions open the work order's uplifts.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
No Vendor opens the work order form on vendor assignment and Vendor Conflict
links each work order, as the vendor reminders did. Header baselines include the
notification bell, and the top bar user menu moves to its own component to stay
within the function-size gate. The vendor e2e now covers the feed contract.
The permission spec asserts the work order renders from the queue item
and, separately, that only the approved-on-WO breakdown degrades to
Unavailable. The Requested At assertion derives the local calendar day
so it holds in every runner time zone.
Header bell panel and /notifications page share one feed from GET /api/notifications: labeled sections with live counts, unread styling, session-only dismiss and clear all (acknowledge rows excluded), and deep links to the unassigned queue and a work order's tab via ?wo=&tab=.
Two sections, Work order and Uplift request, divided by a single rule.
Work order shows Service, Vendor / Technician, Assigned Dispatcher and
Scheduled from the queue item, so it no longer fetches the work order
and no longer degrades to Unavailable for account-scoped staff. Uplift
request shows Requested By, Requested At, the justification in a
bordered block, the approved-on-WO breakdown and a horizontal
attachment row whose chips open the evidence in a new tab. Every field
renders data or an explicit placeholder. The footer shows Reject and
Approve for pending uplifts, Revoke for approved ones and nothing for
read-only records; closing is the header X.
Three real behavior deltas flagged in review, each closing a gap
against the "no behavior change" claim:
- use-vendor-portal-session.ts: add retry:false and
meta:{suppressErrorToast:true} so an invalid/expired token fails
fast with a single request and no duplicate global toast (this was
also the root cause of the failing e2e test - the toast and the
inline error message both had role="alert", tripping a strict-mode
locator match)
- task-template-schema.ts: trim the name before validating so a
whitespace-only name is rejected, matching the old manual
form.name.trim() check
- task-template-detail-form.tsx: block Enter-triggered implicit
submission on the Template Name / Description inputs, since the
original page had no <form> element and Enter did nothing
- task-template-items-field.tsx: surface a visible error when a
loaded item has empty text, since the schema already blocked save
in that case but gave no way to see why; use-task-template-editor.ts
now eagerly validates after loading a template so this reflects
reality immediately instead of only after an unrelated edit
Also extract every user-facing string in the task-templates feature
into TASK_TEMPLATE_COPY (task-template-constants.ts) instead of
inline literals scattered across 5 files.
6 new regression tests cover all of the above; full suite (36 tests
across the 2 refactored areas) still green.
vendor-portal-provider.tsx and task-templates.tsx mixed data-fetching,
state, and rendering in one file, with no caching and hand-rolled form
state. This is a pure refactor with no behavior change.
- vendor-portal-provider.tsx: replace manual useEffect/useState fetch
with useVendorPortalSession (useQuery), closing the token race
condition the old key={token} remount was working around
- task-templates.tsx: split into useTaskTemplateEditor hook plus 4
presentational components; replace hand-rolled form state with
react-hook-form + zod validation; replace the "new" string sentinel
with a NEW_TEMPLATE_ID constant
- add 29 tests (hook, component, and full-page integration) confirming
identical behavior to the original code
useServicesRegistryController exceeded the changed-file max-lines-per-function
budget (158 > 150). Move the add/edit form lifecycle into a useServiceEditor
sub-hook; the controller's public return shape and behavior are unchanged.