#Contractor shifts and profile
Module id: MOD-CONTRACTOR-SHIFTS
Doc profile: capability
Contractors browse and claim open marketplace shifts, manage their My shifts membership (including cancel), and maintain profile fields, documents, and preferred-company visibility on /contractor/shifts. Shared Detailed job view and Row actions stay owned by MOD-COMPANY-SHIFTS — this module consumes Row actions by slot and uses a local detail group, not a forked company reusable.
#Purpose
Signed-in contractors need one authenticated home to find claimable work, open shift detail, claim or cancel membership, and keep identity documents and skills current. Observable outcomes are membership writes on shifts / profiles (contractor lists and shift lists), optional company notification on cancel (in-app and/or Postmark after the grace window), profile and documents persistence under owner RLS, and a 15-minute grace schedule hook at claim time (worker body owned by MOD-TIME-TRACKING).
#Scope boundaries
| In scope |
Out of scope |
Route /contractor/shifts under cabinet workspace; panels for My shifts, Shift list, local detail, profile |
Workspace chrome Header / SideBar (MOD-SHELL) |
| Filters (region, company, skill, date, rate) + search by job title; claim confirm |
Company post/edit/approve dashboard (MOD-COMPANY-SHIFTS) |
Local G-Detailed view of the shift for tab=detail |
Forking DetailedViewJob reusable (sole owner MOD-COMPANY-SHIFTS) |
| My shifts Upcoming / Completed, cancel with reason + dependability deduction |
Time-tracking UI and grace-delay worker recursion (MOD-TIME-TRACKING) |
| Profile save, seven document types, preferred-companies popup |
Stripe Connect / KYC / payouts (MON-MARKETPLACE / MOD-PAYMENTS) |
Create chat row + navigate to /chat from detail Message |
Chat thread body (MOD-CHAT) |
| Slot shared Row actions instance |
Copying Row actions element definition into a contractor-only tree |
#Actors
| Actor |
Behavior in this module |
| Contractor |
Signed-in profiles.role = contractor. Browses claimable shifts, claims/cancels, edits profile and documents. Unapproved contractors (profiles.approved false) cannot claim. |
| Company (indirect) |
Receives cancel notification when scheduled paths fire; does not use this page as primary host. |
| Session client |
Anon-key Supabase client for reads; claim/cancel membership mutations via JWT RPC / edge — never service-role in the browser. |
| Scheduler / edge |
Claim schedules the 15-minute delay API equivalent; cancel may cancel that schedule and notify the company. Delay worker body is MOD-TIME-TRACKING. |
#Capability composition
One host route with mutually exclusive panels driven by URL tab. Empty tab or myshifts shows My shifts; shiftlist shows the marketplace list; detail shows local shift detail; contractors shows profile. Nested query shift={id} binds detail. Shared Row actions mounts inside My shifts rows; company Detailed job view is not instantiated here.
flowchart TB
subgraph host["Contractor shifts host"]
My["My shifts"]
List["Open shift list + filters"]
Detail["Local shift detail"]
Profile["Contractor profile"]
end
subgraph shared["Shared from company module"]
RA["Row actions slot"]
end
List -->|"open row"| Detail
My -->|"details"| Detail
Detail -->|"claim / cancel"| My
My --> RA
Profile -->|"preferred companies popup"| Profile
Bubble evidence for panels and workflows is under Traceability / IR evidence.
#Primary journeys
Journeys are reconstructed from contractor_shifts_profile PageLoaded / ButtonClicked workflows (ChangePage, Show-Hide, MakeChangeCurrentUser, ChangeThing, ScheduleAPIEvent, NewThing notification, connectors). Chat thread UI and grace-delay approve recursion stay out of scope where noted.
#J1 — Enter contractor shifts and switch panels
- A signed-in contractor opens
/contractor/shifts (with or without tab).
- Signed-out visitors redirect to
/; admin visitors redirect to /admin; company visitors redirect to /company/shifts.
- Empty
tab or tab=myshifts shows My shifts; tab=shiftlist shows the open-shift list; tab=detail shows local detail; tab=contractors shows profile.
- URL
message=welcome shows Welcome popup; message=unapproved shows the unapproved claim-pending popup.
- Session/role read failure shows a clear error or redirect — never a blank authenticated shell.
#J2 — Browse, filter, and open shift detail
- On
tab=shiftlist, the contractor sees search placeholder “Search for job” and filter labels Region, Company, Skill set, Select shift date, Select rate.
- The open-shifts list loads claimable marketplace shifts not already on the contractor’s membership, with columns Job Title, Location, Company (plus Shift Time / Shift Date / HR / Action per list chrome).
- Empty list shows “No result found.”; load/filter failures are visible.
- Opening a row navigates to
/contractor/shifts?tab=detail&shift={id} and shows the local detailed view (Skillset, Location, Start Date/Time, Wisconsin time, Hours required, Hourly Rate, Description, Claim / Cancel / Message).
- Detail does not mount or fork the company Detailed job view reusable.
#J3 — Claim an open shift
- On detail, Claim opens Popup Claim Shift with confirm copy, Confirm / Discard, and the 15-minute grace warning.
- If
profiles.approved is false, claim stays blocked (unapproved popup / disabled claim) — no membership writes.
- Confirm adds the contractor to the shift’s contractor list, adds the shift to the contractor’s shift membership, schedules the 15-minute grace delay job, stores the schedule id on the user, and hides the popup.
- Discard closes without membership writes.
- Message creates a chat row for contractor + shift company and navigates to
/chat (thread UI remains MOD-CHAT).
- Mutation failures show inline/page errors; grace worker body beyond enqueue is MOD-TIME-TRACKING.
#J4 — Manage My shifts and cancel
- On My shifts, Upcoming vs Completed shifts toggles list mode (
active_shifts vs closed_shifts) — only one mode active.
- The list shows the contractor’s membership rows (Shift Name, Location, HR, Company, Skillset, Worked Hours, Date) or “No result found.”
- Details reuses the same local detail panel (
tab=detail&shift={id}) — no second detail tree.
- My shifts rows slot shared Row actions (company-owned) behind the existing instance marker — no contractor-only fork of the definition.
- Cancel opens Popup Cancel Shift; Confirm removes membership lists, records cancellation reason, adjusts dependability from the negatives option, cancels any pending grace schedule, may create an in-app company notification when the grace schedule id is empty, may send company Postmark/connector email when Current Date/Time is after shift start + 15 minutes, and removes the shift from Upcoming immediately on success.
- Cancel/notification failures map once and stay visible.
#J5 — Update contractor profile and documents
- On
tab=contractors, profile shows General Information (legal names, email, skills summary, four skill-set options, state, city, dependability score) and document uploaders.
- Seven uploaders map 1:1 to document types: driver’s license, resume, mask fit test, diplomas, hearing examination OSHA, references from two foreman/managers, specialty credentials.
- Picture uploader updates preview; Save persists profile image plus name, skills summary, skill set, state, and city.
- State/City use onboarding CSC helpers (no private CSC headers in the SPA); changing state clears city.
- Optional password fields run UpdateCredentials only when filled and matching.
- “See which companies prefer me” opens Popup Companies listing preferred companies (Company Name, Location, Important Contacts) — do not read the empty
current_contractors_info type.
- Save/upload failures show visible errors; lists/docs stay unchanged on failure.
#Vertical slices (decompose)
| Item |
Product outcome |
| CSHIFT-BROWSE-APPLY |
Host route, role gates, welcome/unapproved messages, shift list + filters, local detail, claim, message→chat, shifts migration + claim enqueue. |
| CSHIFT-MYSHIFTS |
My shifts panel, Upcoming/Completed, cancel popup + membership/dependability writes, shared Row actions slot. |
| CSHIFT-PROFILE |
Profile panel, documents, skills/preferences, preferred-companies popup, profile/docs migration. |
#Key UI states
| State |
When |
User sees |
Empty tab / myshifts |
URL tab empty or myshifts |
My shifts list |
shiftlist |
tab=shiftlist |
Filters + open shifts RG |
detail |
tab=detail + shift id |
Local detailed view |
contractors |
tab=contractors |
Profile + documents |
message=welcome |
query message welcome |
Welcome popup |
message=unapproved |
query message unapproved |
Unapproved popup |
| Filters open |
filter chrome toggled |
Region / Company / Skill / date / rate controls |
| Upcoming vs Completed |
My shifts sort custom state |
Matching membership rows or empty copy |
| Claim popup open |
Claim control |
Confirm / Discard + grace warning |
| Cancel popup open |
Cancel control |
Reason picker + Confirm / Discard |
| Unapproved contractor |
profiles.approved false |
Claim blocked |
#Navigation contracts
| Label / trigger |
Behavior |
Empty tab |
Show My shifts (nav myshifts or empty) |
| My shifts (sidebar) |
?tab=myshifts (nav) |
| Shifts / Shift list (sidebar) |
?tab=shiftlist (tabs — preserve tabs vs nav split) |
| Open detail |
?tab=detail&shift={id} (tabs detail) |
| Profile (header / welcome CTA intent) |
?tab=contractors (nav) for profile panel |
| Welcome dismiss (export-wired) |
Hides welcome; ChangePage stays on host with Shift list tab value — see HQ-1 |
| Unapproved dismiss |
Hides popup; ChangePage sets host tab from option tabs value upcomingshifts (display MyShifts) — distinct from nav myshifts used by My shifts panel visibility |
| Message on detail |
Create chat → /chat with company user param |
| Preferred companies |
Open / close Popup Companies only |
#Style and fidelity
Contractor shifts uses the cabinet workspace outlet. Transcribe visible copy and stamp data-bubble-id on migrated roots: page, Shift list, My shifts, local detail, profile, claim/cancel/welcome/unapproved/companies popups, filter group, and primary RG roots. Shared tokens come from the app-level stylesheet that SHELL-LAYOUT (MOD-SHELL) delivers out of IR styles (ir/slices/styles____part_all.json — 49 styles, 8 color tokens, 1 font token); this cycle's module map has no separate design-system module, so do not defer a token to one. Preserve Wisconsin time copy on detail date/time fields.
#Failure modes
| Case |
Expected behavior |
| Signed-out on host |
Redirect to /; no contractor panels. |
| Admin / company on host |
Redirect to /admin or /company/shifts. |
| Unapproved claim attempt |
Block membership writes; show unapproved messaging. |
| Open-shift list / filter query failure |
Plain-language error; no silent empty success. |
| Claim confirm failure |
Inline/page error; leave membership unchanged; keep or dismiss popup honestly. |
| Cancel confirm / notification failure |
Visible error; Upcoming membership unchanged on hard failure. In-app company notification (when grace schedule id empty) and Postmark/connector email (when Current Date/Time is after shift start + 15 minutes) are separate paths — do not invert the Postmark gate into a within-15-minutes-of-start window. |
| Profile / document / image save failure |
Visible error; prior profile/docs unchanged. |
| Password mismatch on Save |
Focus confirm; do not UpdateCredentials. |
| Chat create failure |
Visible error; do not pretend navigate succeeded. |
| Grace-delay edge unavailable at claim |
User-visible failure after or instead of incomplete schedule persist — do not invent approve recursion here. |
#Needs human clarification
| Id |
Question |
Why unclear (plain + IR) |
Impact |
| HQ-1 |
Should Welcome's "Go to My Profile Page" navigate to the profile panel (tab=contractors) as decompose CSHIFT-BROWSE-APPLY states, or to the Shift list panel as the export ButtonClicked workflow writes? |
Plain: The button label says profile, but the wired ChangePage sets the host tab from option tabs value shiftlist (display ShiftList), not nav contractors. Implement needs one destination. IR: Welcome control workflow bTIoX ChangePage sets tab = display of option.tabs / shiftlist. Profile panel visibility is option.nav / contractors (element_condition:bTJWU0:0); Shift list visibility is option.tabs / shiftlist (element_condition:bTHaf:0). Decompose CSHIFT-BROWSE-APPLY expects profile (tab=contractors) — that conflicts with the export-wired destination. |
blocks_implement |
#Traceability
| Product concept |
Target (route / component / table) |
Bubble evidence |
| Contractor shifts page |
/contractor/shifts, ContractorShiftsPage |
bTHMg |
| Shifts host group |
page chrome |
bTHQn |
| Shift list panel |
ShiftListPanel |
bTHaf, element_condition:bTHaf:0 |
| Filters group |
ShiftFilters |
bTHbT, bTHax, custom_state:bTHbT:* |
| Search box |
job title filter |
bTHbH |
| Open shifts RG |
open marketplace list |
bTHeC |
| Open detail from row |
tab=detail&shift= |
bTKDt, bTHeD |
| Local shift detail |
ShiftDetailPanel |
bTHUv, element_condition:bTHUv:0 |
| Claim button / popup |
ClaimShiftDialog |
bTHWW, bTHme, bTKEE, bTKFR |
| Message → chat |
create + /chat |
bTHXG, bTMkF |
| Welcome / unapproved |
message popups |
bTHkC, bTHoJ, bTIoR, bTIoX, bTIpB |
| PageLoaded gates |
redirect signed-out / roles |
bTKuP, bTKnt |
| My shifts panel |
MyShiftsPanel |
bTHRM, element_condition:bTHRM:0 |
| Upcoming / Completed |
sort custom state |
bTHRZ, bTHRq, bTKEV0, bTKEc0, sort |
| My shifts RG |
membership list |
bTHTF |
| Cancel button / popup |
CancelShiftDialog |
bTHWc, bTHnb, bTKVI, bTKVg |
| Cancel in-app notify |
NewThing notification when schedule id empty |
bTNLS |
| Cancel Postmark/connector |
notify when (start + 15m) < now |
bTNMm |
| Row actions instance |
slot company component |
bTHUp, bTHTn |
| Profile panel |
ContractorProfilePanel |
bTJWU0, element_condition:bTJWU0:0 |
| Profile save / picture |
PATCH profiles |
bTJYG0, bTJWm0, bTKGb, bTKFq |
| Preferred companies popup |
read preferred list |
bTJcT0, bTJdn0, preferred_list_user |
| Documents |
documents + docs_type |
documents, docs_type |
| Shifts table |
public.shifts |
shifts |
| Approval gate field |
profiles.approved |
approve1_boolean |
| Tab option values |
?tab= from tabs / nav |
tabs, nav |
| Rate filter options |
filter attrs min/max |
rate_filter |
| Cancel reasons |
reason enum |
reason_of_cancellation |
| Skill set options |
profile multi-select |
skill_set (option values) |
| Dead preferred-info type |
omit |
current_contractors_info |
| Shared detail reusable (not forked) |
company sole owner |
bTIgC |
| Grace delay API |
enqueue only here |
bTKPV (via claim ScheduleAPIEvent) |
#Source → target naming (under Traceability)
| Source (Bubble) |
Target |
Layer |
contractor_shifts_profile / bTHMg |
/contractor/shifts |
route |
G-Detailed view of the shift |
local ShiftDetailPanel |
ts |
DetailedViewJob |
do not fork; company DetailedViewJob |
ts |
RGaction |
shared ShiftRowActions slot |
ts |
shifts |
shifts |
sql |
job_title_text |
job_title |
sql |
date_date |
shift_date |
sql |
contractor_list_user |
shift contractor membership |
sql |
shifts_list_custom_shifts |
profiles shift membership |
sql |
contractor_rates_number |
contractor_rate |
sql |
approve1_boolean |
profiles.approved |
sql |
short_summary_about_your_experience_text |
skills_summary |
sql |
profileimage_image |
profile_image |
sql |
dependability_scoup_number |
dependability_score |
sql |
preferred_list_user |
preferred_company_ids / preferred companies |
sql |
docs_list_custom_documents |
documents |
sql |
schedule_15_min_delay_text |
grace schedule id column |
sql |
option nav / tabs db_values |
tab query string |
doc/route |
#IR evidence
#Module evidence roots
bTHMg — contractor_shifts_profile page (/pages/bTHMh); Shift list, My shifts, local detail, profile, claim/cancel/welcome workflows
- Shared segments (consume, do not fork):
bTIgC DetailedViewJob (company-only instances), bTHTn RGaction (instance bTHUp on this page)
- Data:
shifts, documents, current_contractors_info (dead empty type)
Supporting segments: ir/slices/bTHMh____root.json, ir/slices/bTHMh__elements____remaining_children.json, ir/slices/bTHMh__elements__bTHQp__elements____remaining_children.json, ir/slices/bTHMh__workflows____part_A.json, ir/slices/edef_bTIgD____root.json, ir/slices/edef_bTHTo____root.json, options tabs / nav / sort / rate_filter / docs_type / reason_of_cancellation / skill_set.
#Path-key ↔ inventory id aliases
| Inventory id |
Export path key |
bTHMg |
/pages/bTHMh |
bTIgC |
/element_definitions/bTIgD |
bTHTn |
/element_definitions/bTHTo |
Claim confirm workflow bTKFR |
/pages/bTHMh/workflows/bTKFT |
Cancel confirm workflow bTKVg |
/pages/bTHMh/workflows/bTKVl |
#Panel visibility (URL tab)
| Panel |
Shows when |
| My shifts |
nav equals myshifts or tab is empty |
| Shift list |
tabs equals shiftlist |
| Local detail |
tabs equals detail |
| Profile |
nav equals contractors |
Preserve SHELL’s split: sidebar My shifts uses nav myshifts; sidebar Shifts uses tabs shiftlist.
#Claim confirm chain (bTKFR)
- MakeChangeCurrentUser — add shift to
shifts_list_custom_shifts when not already present
- ChangeThing — add CurrentUser to shift
contractor_list_user
- ScheduleAPIEvent — API
bTKPV at Current Date/Time + 15 minutes (params user + shift)
- ChangeThing — store schedule id on user
schedule_15_min_delay_text
- Hide claim popup
#Cancel confirm chain (bTKVg)
- Optional NewThing
notification to company when grace schedule id empty (bTNLS) — separate in-app path
- CancelScheduledAPIEvent when schedule id present
- ChangeThing — remove from contractor/show lists; set reason; add to cancel list
- MakeChangeCurrentUser — clear schedule id; remove shift membership; subtract dependability via
negatives cancel-within-4-hours option
- Conditional Postmark/connector notify (
bTNMm) when ElementParent.data_start_date plus 15 minutes is less than Current Date/Time — i.e. after the 15-minute grace window past shift start (fires when (start + 15m) < now, not within 15 minutes of start)
- Reset inputs + hide popup
#Option sets (db_value → display)
| Set |
Values used here |
nav |
myshifts → MyShifts; contractors → Contractors |
tabs |
shiftlist → ShiftList; detail → DetailShift; upcomingshifts → MyShifts; welcome / unapproved for message param |
sort |
active_shifts → ActiveShifts; closed_shifts → ClosedShifts |
docs_type |
seven document db_values listed in J5 |
reason_of_cancellation |
sick, family_emergencies, unexpected_personal_issues, other |
skill_set |
concrete_finisher, concrete_laborer, concrete_walls, concrete_carpenter |
rate_filter |
display bands $1–20 … $80–100; wire db_values include typo _40_600 for $60–80 — persist wire values; use attrs min/max for filtering |
#Privacy / residuals
current_contractors_info — dead empty type; preferred companies use preferred_list_user only.
bTIgC — answered structurally: no instance on this page; local detail only.
- Membership and dependability writes require server-authorized paths (MON-CLIENT-WRITES).
- Grace-delay worker / tracking approve recursion remains MOD-TIME-TRACKING.
#Decompose refs
CSHIFT-BROWSE-APPLY
CSHIFT-MYSHIFTS
CSHIFT-PROFILE