18 SEPTEMBER 2026 · capExpertApp + capExpertAPI · project view, tab 3 and build-team
The screens, and what they call
The front end was already written for something close to this schema — it edits per day and per facility, then collapses to date windows only because the endpoint stores windows. Most of the work here is removing that round trip and letting each person carry their own hours.
How the current UI fitsalmost one to one
capture_visits row; each person on it is a capture_visit_members row with their hours. The collapse is deleted.CAP-xxxx id; Edit and Delete toast “isn’t available yet”with the schemaReal capture_schedules rows with their CS- code; Edit and Delete call real endpoints.capture_visits.hours and each person’s hours.capture_project_blackout_dates, any number of rows; the DTO is already an array, so its shape does not change.Screen 1 — the Scheduling Assistant tab?project=CP-…
The project view’s third tab, where the team roster card is today.
Header card. Project code and name, the target window, one chip per blackout row (07 Oct–08 Oct, 10 Oct), and the daily cap.
Schedules table. The same columns the roster table has now: ID (CS-…), Facility (names with a +N overflow chip), Users (names, a crown on the lead), Durations (start → end), Actions (Preview · Edit · Delete). An empty state with a “Build team” call to action.
Preview sheet. The right-side sheet as today: a window summary (N facilities · M assigned users · D days), then one card per visit day with a Facility · Hours · Assigned users table, each person shown with their hours and the crown on the lead.
Screen 2 — build team?project=1
- Window. Start and end pickers; blackout days are struck out and skipped in the day rail, as now.
- Facilities. The project’s sites, minus those already visited under another schedule — R3 shown as a disabled option reading “in CS-…”. A picked facility is stored with its first visit day; until then it is a selection on screen.
- Days. Per day, one row per facility with planned hours; add and remove rows — R4.
- People. For each person on a row, an hours field defaulted from the visit. Beside the name a “remaining today: X h” chip, computed across all projects from the availability endpoint. The row turns red and Save is blocked when the hours would exceed the cap (“would reach 9 h > 8 h”) or the person is already on that facility that day.
- Save. Back to screen 1 with the new
CS-row.
The dummy UIdeliverable 4
A self-contained mockup of both screens, built as one page with mock data shaped exactly like the four tables and client-side validators that mirror R3–R6, so the demo cannot show a state the schema would reject. It copies the app’s tokens — #414d6a ink on #f5f6f8 surfaces, #e9f0fd borders, the light-blue accent, the right-side sheet, the column table, the outline and primary buttons — and mimics both URL forms. It is specified here and built on request; it is not part of the repositories.
The API contractcapExpertAPI, primary role
Existing routes keep their paths and change their data source. New routes treat a schedule as a record.
lead_user_id / project_lead_id.{ leadId?, startDate, endDate, visits: [{ siteId, date, hours, members: [{ userId, hours }] }] }. A facility with no visits is not persisted.Errors, mappeddatabase → 400
check_violation from the cap trigger — at COMMITapi400 with the trigger’s message (“booked 9.00 hours on 10 Feb, over the 8-hour daily cap”); caught around transaction.commit(), not only around the insertcapture_visit_members_visit_user_uniqueapi400 — “X is already on <facility> on 10 Feb — change their hours instead”capture_visits_site_one_scheduleapi400 — “<facility> already belongs to schedule CS-…”capture_visits_site_date_uniqueapi400 — “<facility> is already scheduled on 10 Feb”check_violation from either guard triggerapi400 naming the date — blackout, or outside the windowNever ignoreDuplicates on any of these inserts: a swallowed violation is a silent duplicate row.