Design specification · build-ready · Support · Scheduling
The exhaustive design specification for RiverSync's centralized visit scheduling: the surface a customer uses to confirm and reschedule the slots RiverSync proposes, a RiverSync dispatcher uses to propose / assign / reschedule / complete visits across every tenant, and a servicing partner uses to work the visits assigned to it. It is specified as one reusable surface — the same command bar, calendar, upcoming rail and visit detail, reskinned per app by a single persona setting (and an optional partner scope) — so Portal, Admin and Partners never diverge. Written so an implementer needs no further decisions: each region, state, class, icon and persona rule is named.
The service scheduling system is the shared visit calendar for preventive maintenance and on-site incident / install / remote-advisory visits. RiverSync (or a servicing partner) proposes one or more candidate slots; the customer confirms one or asks to reschedule; the confirmed visit is assigned to an engineer and rides a lifecycle to completion. Every party works the same calendar — only what they may see and do changes.
One immersive page: a full-width context-aware command bar, then the DS Calendar filling the body with an upcoming-visits rail overlaid at the right, and a visit detail off-canvas that docks to the left of that rail. The surface is identical across the three apps; the differences are captured entirely by persona (+ partner scope).
The five W's of every visit are visible before it is opened — what (type tag + title), which device (id), where (site), when (day + time range), what state (status badge + colour rail).
The component takes a single persona prop (and, for a partner, a scope — its organization). Everything app-specific — the shell chrome (App pill, nav, tenant), which visits are in view, the available lifecycle actions and the wording — derives from it. There is no forked code per app, and no in-app persona switcher — each app deploys its own persona-locked page through the shared mounter, so a customer only ever sees the customer view (in Portal), a dispatcher the dispatch view (in Admin), a partner the scoped partner view (in Partners).
| persona | App / pill | Signed-in party | What this persona can do |
|---|---|---|---|
| customer | Portal · --portal | Customer (Sanyodenki) | Sees every visit on its own fleet. Confirms a proposed slot, requests a reschedule, opens the linked ticket. Cannot propose or cancel — RiverSync owns dispatch. |
| riversync | Admin · --admin | RiverSync dispatch | Sees every tenant's visits. Proposes visits, confirms on behalf, reschedules, marks completed, cancels and re-proposes. The full dispatch authority. |
| partner | Partners · --partners | Servicing partner (Nera) | Sees only the visits assigned to it (servicer === scope), across every customer it covers. Proposes slots for its devices, reschedules and marks its visits completed. |
The UI never invents fields. Every rendered value maps to the VisitProposal → Visit model owned by the Field service domain and defined in SPEC-ERD-FLD. The surface presents two ERD records as one "visit" row: while a slot is being negotiated the record is a VisitProposal with its VisitProposalOptions; once a slot is confirmed it is the accepted Visit. Field names below are the presentation contract the prototype reads.
| Field | Key | Meaning & UI use |
|---|---|---|
| Id | PK | SV-NNNN — calendar block, rail row, detail header. (Maps to VisitProposal.Id / Visit.Id.) |
| Type · Kind | preventiveincidentinstallremote — the type tag + icon + calendar tone. (ERD Visit.Kind + VisitProposal.SourceKind.) | |
| Status | proposedconfirmedrescheduledcompletedcancelled — the negotiation lifecycle (§9); drives the status badge + colour rail. | |
| DeviceId | FK | → Device · 0..1 (null = fleet-wide window). The linked device chip; fleet visits service the whole fleet. |
| SiteId | FK | → OrganizationSite. The location line + the branded VectorMap pin. |
| EngineerUser · Servicer | FK | → the assigned engineer + their org (RiverSync field XOR a servicing partner). The dispatch line; Servicer is the partner-scope key (DM-59). |
| Tier | FK | → the device's maintenance-agreement tier (Silver / Gold) · 0..1. The TierBadge in Linked. |
| TicketId | FK | → ServiceTicket · 0..1 — the support thread the visit was arranged in; the Linked ticket chip. |
| Priority · Origin | high / medium / low + a one-line origin (alarm id, PM trigger, provisioning) shown under the engineer. | |
| Scope | The free-text description of what the visit covers — the detail body paragraph. | |
| Start · End · AllDay | The confirmed window (ISO); AllDay blocks span the day. (ERD Visit.ScheduledAt.) | |
| RescheduledFrom | The previous start when moved — the "Moved from …" line (§9). | |
| History[] | Ordered activity entries {when, by, text} — the Activity log. |
| Field | Key | Meaning & UI use |
|---|---|---|
| ProposalId · OptionNo | PF | Parent proposal + slot ordinal. Rendered as the slots[] radio list on a proposed visit. |
| StartAt · EndAt | The candidate window — one selectable .pv-sch__slot row; the first is the recommended slot and seeds the calendar block before confirmation. | |
| WindowLabel | Human label (e.g. Mon 9–12) — the slot row caption. |
Below the shell chrome the page is a vertical stack: a full-width command bar, then a body that fills the remaining height — the DS Calendar across the full width, the upcoming-visits rail lifted out of flow and overlaid at the right edge, and the visit detail off-canvas docked to the left of that rail. The calendar never reflows when the rail or detail open; the page itself does not scroll.
| Region | Sizing | Specification | |
|---|---|---|---|
| 1 | Command bar .pv-cmdbar | full width · 40px | Fixed at the top (height 40px, matching the shell top bar). Left: identity tile (Schedule + live counts). Middle: the triage command. Right: search + the primary (Propose visit / Today). Never scrolls. (§6.) |
| 2 | Calendar body .pv-sch__cal | fills | The DS Calendar (year ⇄ month ⇄ week) fills the width. Scroll-through-time at the inner boundary moves the period. (§7.) |
| 3 | Visit block | auto | One calendar event per visit — {type short} · {device}, toned by status. Click opens the detail. |
| 4 | Busy-day highlight | auto | In the year view a day carrying scheduled visits gets an accent background (not just the DS dot); today keeps its solid fill. (§7.1.) |
| 5 | Visit detail .pv-schoc | 440px · docks left of rail | The off-canvas reader. Absolute inside the calendar body (never over the top bar or command bar), docked to the left of the upcoming rail (right: var(--sch-rail-w)) so the rail stays visible. Collapses to a 40px rail. (§8.) |
| 6 | Map toggle | auto · pinned bottom | The site map sits at the bottom of the detail as secondary context; the location title toggles it open / closed. (§8.) |
| 7 | Upcoming rail .pv-sch__rail | 364px / 40px | Lifted out of flow and overlaid at the right edge (never squeezes the calendar). Filter + the upcoming list; collapses to a 40px clickable stub (chevron + filter + count). (§7.2.) |
Grid: the body is .pv-sch{position:relative; --sch-rail-w:364px;}; the rail is position:absolute; right:0; z-index:40 (collapsed → .pv-sch.is-railsm{--sch-rail-w:40px}); the detail is .pv-schoc{position:absolute; right:var(--sch-rail-w); z-index:60}. The page is immersive (.pv-tkpage--immersive): no page padding, fills below the 40px top bar. Under 1120px the rail narrows to 320px; the detail offset follows it.
The same context-aware navbar idiom as the ticket surface and Pipeline: a full-width, square, hairline-divided strip. It carries the schedule's identity + live counts, a triage command, search, and a persona-appropriate primary. Time navigation (period range, prev/next, year/month/week) lives in the calendar's own head — the command bar never duplicates it; it owns search, triage and dispatch.
| Element | Icon · style | Shown for | Behavior |
|---|---|---|---|
| Identity + counts | calendar | all | "Schedule" + a live meta line: {upcoming} upcoming · {proposed} awaiting confirmation (scoped per persona). |
| Triage | alert · warn | all · proposed>0 | A warn-tinted {n} to confirm (customer) / {n} awaiting (managers) — jumps the calendar to, and opens, the next proposed visit. |
| Search | search | all | Free-text over id · title · device · engineer · servicer · site; filters both the calendar and the upcoming rail live. Clear button when set. |
| Propose visit | plus · primary | riversync · partner | Right edge. Opens the propose off-canvas (the slot composer). The dispatch primary. |
| Today | calendar · primary | customer | Right edge. Jumps the calendar to the current day. The customer primary (the customer never proposes). |
The dispatch primary opens a propose off-canvas (the same .pv-schoc shell as the detail) — a small form: visit type, device, assigned engineer, date, start/end and an optional scope. For a partner persona the engineer list is pre-filtered to that partner's own engineers, and the copy reads "You propose a slot — the customer confirms or reschedules." The proposed visit lands as status=proposed with one slot, attributed to the proposing party.
The body is the DS Calendar — year, month and week views with the DS zoom transitions, its own period title + prev/next + Today + view switcher in its head. Each visit is one calendar event, coloured by status; clicking a block opens the visit detail.
| Element | Spec |
|---|---|
| Event | {type short} · {device or "Fleet"}; tone = status (proposed warning · confirmed accent · rescheduled info · completed/cancelled muted/done). Assignee = the partner engineer (RiverSync field shows no avatar). |
| Scroll-through-time | At the inner scroll boundary (year list / week scroll), an overscroll step advances the period (year ± 1, week ± 7d); month view scrolls week-by-week natively. A debounced Scroll to move through time hint sits at the body foot. |
| Today | The command-bar / calendar-head Today jumps date back to PSD.NOW. |
A day with visits reads as a filled day. In the year view a busy day gets an accent-soft background + accent text (.pv-sch__cal .rs-calyear__day.is-busy), tinted like the weekend/today affordances — the bare 3px DS dot was too faint. Today keeps its solid accent fill even when busy.
The rail overlays the right edge of the calendar (it never squeezes it). It lists the persona's upcoming visits (future, non-terminal, matching the active filter + search), each a row with type tag, title, id · device · site, the day + time, and a status badge. A header carries a filter toggle, the title + count, and a collapse chevron.
| Element | Spec |
|---|---|
| Filter .pv-schfilter | Type · Status · Site chip rows (multi-select), with a Clear. An active filter shows a warning dot on the toggle. The command bar has no filter — it lives only here. |
| Collapsed stub | A 40px in-flow rail: an expand chevron, a filter icon (expands straight into the filter panel), the count + label (vertical), a calendar glyph. The whole stub is clickable to expand (the filter button stops propagation). |
| Row → detail | Clicking a row opens that visit's detail off-canvas (docked left of the rail) and keeps the rail in view. |
Opening a visit slides in the detail — a scrimless, non-blocking panel bounded to the calendar body (it never covers the app top bar or the command bar) and docked to the left of the upcoming rail so the list stays visible. A 40px header (collapse + close, type glyph, title, id) over a scrolling body over a footer of lifecycle actions. The reader leads with the primary facts and ends with the map.
| Region (top → bottom) | Specification |
|---|---|
| Status line | The status badge + a persona-aware blurb ("A slot has been proposed — confirm or reschedule" for the customer; "Proposed to the customer — awaiting their confirmation" for managers). |
| When | Calendar glyph + the confirmed day + time range (or All day); a Moved from … line when rescheduledFrom is set. |
| Scope | The free-text description of what the visit covers. |
| Linked .pv-schdetail__links | Linked + a chip per related record: Device → device view, Ticket → the ticket thread, Gold/Silver agreement (the TierBadge) → maintenance. Each deep-links. |
| Servicing engineer | The assigned engineer line — RiverSync field (hub glyph) or a partner engineer (avatar + org tag) — and the Origin (alarm / PM trigger). |
| Choose a slot · Reschedule | On a proposed visit, the slots[] radio list (Choose a slot to confirm / Proposed slots). The Reschedule mode swaps in date + start/end pickers (the branded DateInput/TimeInput). |
| Activity | The history[] log — each entry's time · author · text. |
| Where (map) .pv-schdetail__map | Pinned at the bottom as secondary context. The location title is a toggle: clicking it expands the branded VectorMap with the site pin; clicking again collapses it (a rotating chevron shows state). Fleet-wide visits show a "Fleet-wide · all sites" line instead. |
A visit rides proposed → confirmed → (rescheduled) → completed, with cancelled as a leaf and re-propose returning a cancelled visit to proposed. Every transition appends a history entry (attributed to the persona) and raises a toast. The footer actions are persona-gated.
| Status | Tone · meaning | Footer actions by persona |
|---|---|---|
| proposed | warning · awaiting confirmation | customer: Open ticket · Reschedule · Confirmed (primary). managers: Confirm on behalf (primary) · Reschedule · Cancel. |
| confirmed | accent · engineer assigned | customer: Open ticket · Reschedule. managers: Mark completed (primary) · Reschedule · Cancel. |
| rescheduled | info · moved & re-confirmed | As confirmed; carries the Moved from … line. |
| completed | success · signed off | Terminal. Open ticket · Close. (Corresponds to Field sign-off, DM-30.) |
| cancelled | neutral · no dispatch | Terminal. Open ticket · Close; managers get Re-propose. |
| Action | Persona | Result | Toast |
|---|---|---|---|
| Confirm slot | customer | Sets confirmed at the chosen slot; assigns the engineer; clears slots. | "Visit … confirmed · Engineer assigned and notified." (success) |
| Confirm on behalf | managers | Same, performed by RiverSync / the partner for the customer. | "Visit … confirmed …" (success) |
| Request reschedule / Reschedule | all | Sets rescheduled, records rescheduledFrom, moves start/end. Customer wording = "Reschedule requested"; managers = "Rescheduled". | "Visit … rescheduled · Moved to …" (info) |
| Mark completed | managers | Sets completed. | "Visit … completed …" (success) |
| Cancel | managers | Sets cancelled. | "Visit … cancelled · No engineer dispatched." (warning) |
| Re-propose | managers | Returns a cancelled visit to proposed with a fresh slot. | "Visit … re-proposed · Awaiting customer confirmation." (accent) |
| Propose visit | managers | Creates a new proposed visit (the propose off-canvas, §6.1). | "Visit … proposed · Sent to the customer for confirmation." (accent) |
Every element resolves to a RiverSync DS v2 component, class, icon or token. The surface is a React page composing the DS calendar + map; the pv-sch* / pv-cmdbar* classes are the Portal-local layer (portal-schedule.css + portal.css), themed entirely through DS tokens.
| Element | Class / component | DS basis |
|---|---|---|
| App pill (top bar) | rs-portalbadge--portal / --admin / --partners | Canonical app badge per persona (shell-chrome.css). Mandatory. |
| Shell | AppShell (hub / admin) · re-tinted pill (partners) | The DS shell; the partner pill is re-tinted where AppShell ships no partners config (§10.1, shared with the ticket spec). |
| Calendar | Calendar (+ CalendarUtils) | DS calendar system — year/month/week, zoom, event popover. |
| Map | VectorMap | DS branded vector map — diamond site pin, no third-party tiles. |
| Tier badge | TierBadge | DS maintenance-tier chip (Silver / Gold + SLA tooltip). |
| Status badge | Badge (tone) | DS status badge — 2px radius, soft tint, sentence case (facts, never counts). |
| Pickers | DateInput · TimeInput · Select · Segmented · Textarea · Field | DS form controls (branded, never the OS picker). |
| Command buttons | pv-cmd pv-cmd--primary / --warn | Command-bar buttons over DS tokens (the pipeline / ticket idiom). |
| Toast | PVToast(title, msg, kind) | Portal toast — success / info / warning / accent. |
| Icons | <Icon name="…"/> | Proprietary icon system (the G table) — never a third-party set. |
Icons in use: calendar · alert · search · close · plus · check · success · history · chevron-down / -up / -left / -right · map-pin · hub · server · ticket · shield-check · filter · x · send · info. Any new glyph is added to the DS G table first, never drawn inline.
One engine, embedded per app. The surface is the single component PV.Schedule, mounted through the shared helper PV.mountScheduleApp (schedule-app.jsx). Each app deploys its own page (Portal · Admin · Partners); they embed the engine, they do not copy it.
Configure by props, not forks. An embedding app passes only: persona (+ partner scope), me, go (navigation) and toast. Visit data + lifecycle live inside the engine. No app reaches in.
The shell is the app's, the surface is shared. Each app wraps the surface in its own shell + App pill; the surface renders only the immersive page content.
Scope & capability are centralized. Partner scoping (servicer === scope) and the canManage action gating are computed inside the component from persona — never re-implemented per app.
| Breakpoint | Behavior |
|---|---|
| > 1120px | Calendar + 364px rail; the detail docks at right:364px. Collapsed rail → 40px, detail follows. |
| ≤ 1120px | Rail narrows to 320px; the detail offset (--sch-rail-w) follows. |
| ≤ 1180px | Command-bar count chip hides; triage + search + primary stay. |
Accessibility: calendar events & rail rows are buttons; status/type pair an icon or text with their tint (never colour alone); the branded pickers are keyboard-operable; the map toggle and rail stub carry aria-expanded/labels; DS zoom & entrance motion honor prefers-reduced-motion. Hit targets ≥ 40px.
The prototype is the source of truth for look & behavior; these are the deltas a production build must close. None changes the model except where flagged.
| Area | As-built | Production target |
|---|---|---|
| Partner actions | propose / reschedule / complete | Model change. DM-59 reads partner schedule as read-only; the chosen product model elevates a servicing partner to act on its assigned visits. Cascade to DM-59 + a PAR- requirement (§12.1). |
| Partner shell | re-tinted pill | First-class partners portal in AppShell (shared with SPEC-UIX-TKT §10.1). |
| Scheduling preferences | not surfaced | Honor SchedulingPreference (DM-58) — preferred windows, blackouts, min-lead — when proposing slots. |
| Slots / proposal | inline slots[] | Persist VisitProposal + VisitProposalOption; emit visit.proposed / confirmed / rescheduled domain events. |
| Execution hand-off | "completed" set here | Hand a confirmed visit to the Field app; let Field's scheduled → enroute → onsite → complete + sign-off drive the final completion. |
| Cross-app sticky state | shared localStorage keys | Namespace sch-view / sch-lens / sch-rail-collapsed per app so a view choice in one app doesn't carry to another. |
| This spec | Traces to |
|---|---|
| Whole document | DM-57 (propose → select scheduling) · DM-58 (preferences) · DM-59 (partner scope) — reused across Portal · Admin · Partners. |
| Data model (§4) | Visit · VisitProposal · VisitProposalOption (SPEC-ERD-FLD), Field service domain (SPEC-DDD-FLD). |
| Process (§9) | SPEC-PWF-SCH · visit lifecycle in SPEC-DWF-FLD. |
| Execution boundary (§4) | Visit.Status + sign-off (DM-27, DM-30) — the Field app (SPEC-APP-FLD). |
| Cross-links (§8) | Device (SPEC-ERD-PTL) · device monitoring (SPEC-UIX-DEV) · ticket (SPEC-UIX-TKT) · agreement tier (SPEC-PRD-MNT). |
| Version | Date | Changes |
|---|---|---|
| 1.1 | 1 Jul 2026 | Added the on-page contents nav (page-local navigation) and cross-linked the new device monitoring spec (SPEC-UIX-DEV) from the traceability cross-links — a visit's Device chip opens the device detail specified there. No change to the surface, model or lifecycle; the shared .pgnav style now lives in comms-design.css. |
| 1.0 | 29 Jun 2026 | First issue — the service scheduling system specified as one reusable surface deployed as a dedicated, persona-locked page inside each app (no in-app switcher): Portal (customer) · Admin (RiverSync dispatch) · Partners (servicing partner, scoped). Scope & distinctions (§1–2), the three personas & the canManage/scope rules (§3), the Visit/VisitProposal/VisitProposalOption presentation contract and the two-lifecycle boundary (§4), layout & grid with annotated wireframe — calendar body, rail overlay, and the detail docked left of the rail (§5), the context-aware command bar + propose (§6), the calendar, busy-day highlight, scroll-through-time & the upcoming rail (§7), the visit detail with the bottom map toggle (§8), the lifecycle & persona-gated actions (§9), design-system mapping, reuse rules & responsive (§10–11), as-built vs target, open questions / proposed requirements incl. the partner-action vs DM-59 conflict, traceability (§12–14). A presentation view over a settled model — no new entities or fields; the partner-write elevation is logged as a proposed DM-59/PAR change for the ERD/PRD cascade. Added to the Design specs family (SPEC-UIX-SCH). |