#TradeForce data model
Target persistence for TradeForce under Supabase (app/supabase/migrations), driven by Bubble IR user types, option sets, and privacy roles. Narrative uses idiomatic Postgres names. Bubble ids stay in the naming map and IR evidence.
#Purpose
Define the schema implement must create so auth, onboarding, shift marketplaces, time tracking, payments, admin, chat, and shell notifications share one coherent database. This is the target schema, not a Bubble inventory dump.
#Stack and identity
- Frontend: React + Vite + TSX (
app/) via Supabase JS client + user JWT. - Backend: Supabase Auth + Postgres RLS + edge functions (
app/supabase/). - Identity: Bubble
user→public.profileskeyed toauth.users.id. Do not create a SQL table nameduser/usersfor domain fields. Email/password live in Supabase Auth; domain flags (role,active,approved,onboarding_complete, rates, Stripe ids) live onprofiles.
#Schema overview
| Target table / enum | Bubble source | Status |
|---|---|---|
| profiles | user | Migrate |
| shifts + shift_contractors | shifts (+ list.user bags) | Migrate |
| time_entries | time_tracking1 | Migrate |
| documents | documents | Migrate |
| chats, messages | chat, message | Migrate |
| notifications | notification | Migrate |
| contractor_skills | skill_set | Migrate |
| custom_rates | custom_rate | Migrate |
| enums from option sets | option_sets/* | Migrate db_values |
| — | company | deleted: true in export; fold into profiles.role=company (no companies table) |
| — | job | deleted: true in export; dead residual |
| — | rates | deleted: true in export; all twelve fields deleted too |
| — | time_tracking | deleted: true in export; do not migrate |
| — | current_contractors_info | deleted: true in export, zero fields; do not migrate |
#Entities and relationships
#profiles (account and role)
Every signed-in actor is a profile row. role is the option.role enum (contractor, company, admin). Company operators are not a separate companies table: Bubble custom.company only carries a name and dead lists, and live company ownership is shifts.company_id plus profiles.role = company. Company onboarding stores the company display name in first_name (same Bubble field as a contractor's legal first name).
Skill rates for verification and payment math are numeric columns on the profile (company_* and contractor_* per trade). Preferred / do-not-book lists are profile↔profile relations.
#Stripe fields (MON-MARKETPLACE fence)
Only two Stripe-related columns exist in the export: profiles.stripe_account_id and profiles.stripe_name. Implement may add those columns for read/display (contractor payments control copy and dashboard URL when an id is present). Out of scope until MON-MARKETPLACE is answered: Stripe Connect onboarding, KYC, writing stripe fields from Connect plugin responses, GetDataFromAPI live balance, payouts/split. Do not invent additional Stripe tables or columns.
#shifts and membership
A shift belongs to a company profile, carries job title, schedule, rates, skill, type, policies, and seats. Claimed contractors are a many-to-many (shift_contractors) rather than only a uuid array on the parent, matching how claim/cancel workflows mutate membership. Cancelled and no-show lists remain explicit relations.
#time_entries
Live time and payment rows come from Bubble time_tracking1 (display “Time tracking”). NewThing actions create custom.time_tracking1. The older time_tracking type is deleted in the export; send_tracking_approve already takes track as custom.time_tracking1 and only its trigger condition still carries the stale btype_id: custom.time_tracking (/api/bTMBh → properties.condition.properties). That event must be rewritten as a queue/cursor job against time_entries (open risk LOG-RECURSIVE) — the stale reference is not a reason to port a second table.
Money fields (paid_amount, admin_paid_amount, sent_for_payment, related shift totals) require server/RPC authority (MON-CLIENT-WRITES / LOG-CLIENT-WRITES): the SPA must not be the sole writer.
#documents and contractor_skills
Documents are typed rows (docs_type enum) owned by a profile. Contractor skills are rows linking a contractor to a skill_set enum value, optional manual text, and experience enum.
#chats, messages, notifications
Chats hold participants; messages hang off chat_id with soft-delete-per-user via deleted_for_ids. Notifications are fan-out rows with recipient and read-by id lists for the shell bell and chat send path.
#custom_rates
Admin day/shift rate overrides: existed vs new rate linked to a shift. Used from admin company detail Save.
#Dead and folded types
All five are marked deleted: true on the type in source/tradeforce-50323.sanitized.bubble (user_types.<id>.deleted). ir/inventory.json does not carry that flag on user_type rows — it keeps it only on user_type.field — so this section, not inventory meta, is the authority on which types are dead (toolkit defect DEF-141). Bubble removes a type's privacy roles together with the type, which is why these five and only these five have no privacy_role entries: that is deletion, not an unprotected table.
- rates: deleted type; all twelve fields deleted too. Do not create a table — use profile skill rate columns.
- job: deleted type; title/skill live on shifts.
- company: deleted type, folded into profiles as above; record fields only as do-not-migrate.
- current_contractors_info: deleted type, zero fields; preferred companies use
preferred_company_ids. - time_tracking: deleted type; do not migrate.
#Constraints and write authority
- RLS mirrors privacy roles: no anon write; owner/participant/admin/company scopes as in the privacy matrix below.
- Pagination + max limit on every collection query (backend pack rule).
- Money and approval transitions go through transactional RPCs/edge, not open client updates.
- File/image fields become Storage paths; Bubble CDN URLs are cutover inputs only.
#Failure modes
- Migrating deleted fields or the deleted
rates/time_tracking/ emptycurrent_contractors_infotypes doubles schema noise without product value. - Treating Bubble
useras the SQL table name collides with Auth and breaks idiomatic Supabase. - Implementing Stripe Connect because stripe columns exist violates MON-MARKETPLACE.
- Client-authoritative updates to
paid_amount/sent_for_paymentviolate MON-CLIENT-WRITES.
#Naming (idiomatic)
Semantic adaptation mode: idiomatic. Frontend stack react-vite-tsx; backend stack supabase. Drop Bubble _text / _boolean / _number / _date suffixes when the Postgres type is clear. Option-set db_value members become enum/check values; displays stay UI copy.
Summary evidence map (gate + human):
| Target | Evidence |
|---|---|
| profiles | Bubble user_type user |
| shifts | Bubble user_type shifts |
| time_entries | Bubble user_type time_tracking1 |
| documents | Bubble user_type documents |
| chats | Bubble user_type chat |
| messages | Bubble user_type message |
| notifications | Bubble user_type notification |
| contractor_skills | Bubble user_type skill_set |
| custom_rates | Bubble user_type custom_rate |
| (no table) | company, job, rates, time_tracking, current_contractors_info |
#chats (Bubble chat)
Participant membership arrays or join equivalent.
| Target | Bubble id | Type | Notes |
|---|---|---|---|
| — | message_list_custom_message |
via messages.chat_id | Prefer FK over list bag |
| participant_ids | user_list_user |
uuid[] | |
| visible_participant_ids | visible_user_list_user |
uuid[] |
Also create on every migrated table: id uuid pk, created_at timestamptz, updated_at timestamptz, and for owner-scoped rows created_by uuid when Bubble Created By is load-bearing (messages).
#company (not a target table) (Bubble company)
Company operators are profiles.role=company; live ownership via shifts.company_id → profiles. Residual answered_structurally in MOD-COMPANY-SHIFTS.
| Target | Bubble id | Type | Notes |
|---|---|---|---|
| — | company_name_text |
text | Do not migrate type; company name → profiles.first_name for role=company |
| — | job_list_custom_job |
list.custom.job | Do not migrate |
| — | shifts_list_custom_shifts |
list.custom.shifts | Deleted — do not migrate |
#current_contractors_info (not a target table) (Bubble current_contractors_info)
Empty type (no fields). Preferred companies use profiles.preferred_company_ids.
No fields in export.
#custom_rates (Bubble custom_rate)
Admin day-specific rate overrides.
| Target | Bubble id | Type | Notes |
|---|---|---|---|
| — | date_date |
date | Deleted — do not migrate |
| existed_rate | existed_rate_number |
numeric | |
| new_rate | new_rate_number |
numeric | |
| — | shift_custom_shifts |
custom.shifts | Deleted — do not migrate |
| — | shift_option_pay_practices |
option.pay_practices | Deleted — do not migrate |
| shift_id | shift1_custom_shifts |
uuid FK → shifts |
Also create on every migrated table: id uuid pk, created_at timestamptz, updated_at timestamptz, and for owner-scoped rows created_by uuid when Bubble Created By is load-bearing (messages).
#documents (Bubble documents)
Owner-linked document rows; type enum docs_type.
| Target | Bubble id | Type | Notes |
|---|---|---|---|
| file_path | file_file |
text | Storage path/URL |
| name | name_text |
text | |
| type | type_option_docs_type |
enum docs_type | Persist db_value |
Also create on every migrated table: id uuid pk, created_at timestamptz, updated_at timestamptz, and for owner-scoped rows created_by uuid when Bubble Created By is load-bearing (messages).
#job (not a target table) (Bubble job)
Legacy; live job title/skill live on shifts.
| Target | Bubble id | Type | Notes |
|---|---|---|---|
| — | job_title_text |
text | Dead type — job title lives on shifts.job_title |
| — | shifts_list_custom_shifts |
list.custom.shifts | Dead type — do not migrate |
| — | skill_set_text |
text | Deleted — do not migrate |
#messages (Bubble message)
Normalized under chats.
| Target | Bubble id | Type | Notes |
|---|---|---|---|
| chat_id | chat_custom_chat |
uuid FK → chats | |
| deleted_for_ids | delete_list_user |
uuid[] | Soft-hide per user |
| — | file_file |
file | Deleted — do not migrate |
| attachment_paths | file_list_file |
text[] | |
| read | read_boolean |
boolean | |
| recipient_id | recipient_user |
uuid FK → profiles | |
| body | text_text |
text |
Also create on every migrated table: id uuid pk, created_at timestamptz, updated_at timestamptz, and for owner-scoped rows created_by uuid when Bubble Created By is load-bearing (messages).
#notifications (Bubble notification)
Header bell + chat send inserts.
| Target | Bubble id | Type | Notes |
|---|---|---|---|
| — | read_boolean |
boolean | Deleted — do not migrate |
| read_by_ids | read_list_user |
uuid[] | |
| body | text_text |
text | |
| recipient_ids | user_list_user |
uuid[] |
Also create on every migrated table: id uuid pk, created_at timestamptz, updated_at timestamptz, and for owner-scoped rows created_by uuid when Bubble Created By is load-bearing (messages).
#rates (not a target table) (Bubble rates)
Every field deleted in export; skill rates live on profiles.
| Target | Bubble id | Type | Notes |
|---|---|---|---|
| — | concrete_carpenter_number |
number | Deleted field on dead rates type — do not migrate; rates on profiles |
| — | concrete_finisher_number |
number | Deleted field on dead rates type — do not migrate; rates on profiles |
| — | concrete_laborer_number |
number | Deleted field on dead rates type — do not migrate; rates on profiles |
| — | concrete_walls_number |
number | Deleted field on dead rates type — do not migrate; rates on profiles |
| — | contractor_concrete_carpenter_number |
number | Deleted field on dead rates type — do not migrate; rates on profiles |
| — | contractor_concrete_finisher_number |
number | Deleted field on dead rates type — do not migrate; rates on profiles |
| — | contractor_concrete_laborer_number |
number | Deleted field on dead rates type — do not migrate; rates on profiles |
| — | contractor_concrete_walls_number |
number | Deleted field on dead rates type — do not migrate; rates on profiles |
| — | existed_rate_number |
number | Deleted field on dead rates type — do not migrate; rates on profiles |
| — | new_rate_number |
number | Deleted field on dead rates type — do not migrate; rates on profiles |
| — | select_date_date |
date | Deleted field on dead rates type — do not migrate; rates on profiles |
| — | select_date_number |
number | Deleted field on dead rates type — do not migrate; rates on profiles |
#shifts (Bubble shifts)
Core marketplace shift rows.
| Target | Bubble id | Type | Notes |
|---|---|---|---|
| completed | _ompleted_boolean |
boolean | Cyrillic С in Bubble display |
| seats_needed | amount_of_needed_contractors_number |
numeric | |
| cancelled_by_ids | cancel_user_list_user |
uuid[] | |
| — | cancel_user_user |
user | Deleted — do not migrate |
| cancelation_policy | cancelation_policy_text |
text | Source spelling cancelation |
| — | company_custom_company |
custom.company | Deleted — do not migrate |
| company_description | company_description_and_expectations_text |
text | |
| company_name | company_name_text |
text | Denormalized display |
| company_rate | company_rates_number |
numeric | |
| company_requested | company_request_boolean |
boolean | |
| company_id | company_user |
uuid FK → profiles | Owning company user |
| no_show_contractor_ids | contractor_don_t_show_up_list_user |
uuid[] | |
| — | contractor_list_user |
via shift_contractors | Claimed contractors |
| contractor_rate | contractor_rates_number |
numeric | |
| data_start | data_start_date |
timestamptz | |
| shift_date | date_date |
timestamptz | |
| — | end_timer_date |
date | Deleted — do not migrate |
| experience | experience_text |
text | |
| hours_required | hours_required_number |
numeric | |
| important_contacts | important_contacts_text |
text | |
| — | job_custom_job |
custom.job | Deleted — do not migrate |
| — | job_list_custom_job |
list.custom.job | Deleted — do not migrate |
| — | job_name_text |
text | Deleted — do not migrate |
| job_title | job_title_text |
text | |
| location_directions | location_directions_where_to_meet_text |
text | |
| — | location_geographic_address |
geographic_address | Deleted — do not migrate |
| — | logget_t__hours__number |
number | Deleted — do not migrate |
| — | logget_t__minuts__number |
number | Deleted — do not migrate |
| max_hours_overtime_policy | max_hours_a_shift_and_overtime_policy_text |
text | |
| paid | paid_boolean |
boolean | |
| reason_of_cancellation | reason_of_cancellation_text |
text | Often option.reason_of_cancellation display/db_value |
| schedule_tracking_job_id | schedule_tracking_text |
text | |
| shift_time_hours | shift_time_number |
numeric | |
| — | shift_timer_custom_shifts |
custom.shifts | Deleted — do not migrate |
| shift_type | shift_type_option_shift_type |
enum shift_type | posted|upcoming|closed |
| visible_contractor_ids | show_contractor_list_user |
uuid[] | Or join; filter visibility |
| — | show_contractor_user |
user | Deleted — do not migrate |
| — | skill_set__option_skill_set |
option.skill_set | Deleted — do not migrate |
| — | skill_set_list_option_skill_set |
list.option.skill_set | Deleted — do not migrate |
| skill_set | skill_set_option_skill_set |
enum skill_set | |
| — | skill_set_text_list_text |
list.text | Deleted — do not migrate |
| — | skill_set_text_text |
text | Deleted — do not migrate |
| — | start_timer_date |
date | Deleted — do not migrate |
| stop_hiring | stop_hiring_boolean |
boolean | |
| total_paid | total_paid_number |
numeric | MON-CLIENT-WRITES — server authority |
Also create on every migrated table: id uuid pk, created_at timestamptz, updated_at timestamptz, and for owner-scoped rows created_by uuid when Bubble Created By is load-bearing (messages).
#contractor_skills (Bubble skill_set)
Per-contractor skill rows (option + free text + experience).
| Target | Bubble id | Type | Notes |
|---|---|---|---|
| — | company_custom_company |
custom.company | Deleted — do not migrate |
| — | contractor_custom_skill_set |
custom.skill_set | Deleted — do not migrate |
| contractor_id | contractor_user |
uuid FK → profiles | |
| experience | expierence_option_expierence |
enum experience | Source spelling expierence |
| skill_manual | skill_manual_text |
text | |
| skill | skill_option_skill_set |
enum skill_set |
Also create on every migrated table: id uuid pk, created_at timestamptz, updated_at timestamptz, and for owner-scoped rows created_by uuid when Bubble Created By is load-bearing (messages).
#time_tracking (not a target table) (Bubble time_tracking)
Type deleted in export; leftover send_tracking_approve param type. Live rows are time_tracking1 → time_entries (LOG-RECURSIVE rewrite).
| Target | Bubble id | Type | Notes |
|---|---|---|---|
| — | approve_boolean |
boolean | Deleted type time_tracking — do not migrate; use time_entries |
| — | break_list_number |
list.number | Deleted type time_tracking — do not migrate; use time_entries |
| — | break_number |
number | Deleted type time_tracking — do not migrate; use time_entries |
| — | break1_number |
number | Deleted type time_tracking — do not migrate; use time_entries |
| — | company_request_boolean |
boolean | Deleted type time_tracking — do not migrate; use time_entries |
| — | contractor_user |
user | Deleted type time_tracking — do not migrate; use time_entries |
| — | end_date |
date | Deleted type time_tracking — do not migrate; use time_entries |
| — | logged__hours__number |
number | Deleted type time_tracking — do not migrate; use time_entries |
| — | logged__minuts__date |
date | Deleted type time_tracking — do not migrate; use time_entries |
| — | logged__minuts__number |
number | Deleted type time_tracking — do not migrate; use time_entries |
| — | logged__second__number |
number | Deleted type time_tracking — do not migrate; use time_entries |
| — | notes_text |
text | Deleted type time_tracking — do not migrate; use time_entries |
| — | schedule_send_approve_text |
text | Deleted type time_tracking — do not migrate; use time_entries |
| — | send_boolean |
boolean | Deleted type time_tracking — do not migrate; use time_entries |
| — | shift_custom_shifts |
custom.shifts | Deleted type time_tracking — do not migrate; use time_entries |
| — | start_date |
date | Deleted type time_tracking — do not migrate; use time_entries |
#time_entries (Bubble time_tracking1)
Canonical time/payment entry table (MOD-TIME-TRACKING / MOD-PAYMENTS).
| Target | Bubble id | Type | Notes |
|---|---|---|---|
| admin_notes | admin_notes_text |
text | |
| admin_paid_amount | admin_paid___number |
numeric | Money — server authority |
| company_approved | approve_company_boolean |
boolean | |
| break_minutes | break_number |
numeric | |
| company_requested | company_request_boolean |
boolean | |
| contractor_id | contractor_user |
uuid FK → profiles | |
| ended_at | end_date |
timestamptz | |
| — | feedback_text |
text | Deleted — do not migrate |
| logged_hours | logged__hours__number |
numeric | |
| logged_minutes | logged__minuts__number |
numeric | Source spelling minuts |
| logged_seconds | logged__second__number |
numeric | |
| company_notes | notes_company_text |
text | |
| contractor_notes | notes_text |
text | |
| paid_amount | paid___number |
numeric | Money — server authority (MON-CLIENT-WRITES) |
| paid_quantity | paid_number |
numeric | Distinct from paid_$; no workflow key writes found — keep column for cutover |
| schedule_send_approve_job_id | schedule_send_approve_text |
text | |
| approval_notice_sent | send_boolean |
boolean | |
| sent_for_payment | send_paid_boolean |
boolean | |
| shift_id | shift_custom_shifts |
uuid FK → shifts | |
| started_at | start_date |
timestamptz | |
| payment_status | status_option_status_paid |
enum status_paid | db_value paid |
Also create on every migrated table: id uuid pk, created_at timestamptz, updated_at timestamptz, and for owner-scoped rows created_by uuid when Bubble Created By is load-bearing (messages).
#profiles (Bubble user)
Supabase Auth holds credentials; profiles is domain identity (not auth.users as the product table).
| Target | Bubble id | Type | Notes |
|---|---|---|---|
| contact_area | 2__contact_area_text |
text | Company onboarding |
| important_contact_information | 2__important_contact_information_text |
text | |
| job_site_information | 2__job_site_information_text |
text | |
| osha | 2__osha_text |
text | |
| pay_practices | 2__pay_practices_list_option_pay_practices |
pay_practice[] | option.pay_practices db_values |
| rules_regulations | 2__rules___regulations_text |
text | |
| rules_protocol | 2__rules_protocol_text |
text | |
| safety_handbook | 2__safety_information_safety_handbook_text |
text | |
| active | active__boolean |
boolean | Activation gate (ACC-AUTH-SURFACE) |
| — | approve_boolean |
boolean | Deleted — do not migrate |
| approved | approve1_boolean |
boolean | Admin verification; SignUp starts false |
| city | city_text |
text | CSC API display name |
| company_rate_concrete_finisher | company_concrete_finisher_number |
numeric | |
| company_rate_concrete_laborer | company_concrete_laborer_number |
numeric | |
| company_rate_concrete_walls | company_concrete_walls_number |
numeric | |
| company_rate_concrete_carpenter | company_rates_number |
numeric | Company-side skill rate |
| contractor_rate_concrete_finisher | contractor_concrete_finisher_number |
numeric | |
| contractor_rate_concrete_laborer | contractor_concrete_laborer_number |
numeric | |
| contractor_rate_concrete_walls | contractor_concrete_walls_number |
numeric | |
| contractor_rate_concrete_carpenter | contractor_rates_number |
numeric | Contractor-side skill rate |
| — | custom_rate_custom_custom_rate |
custom.custom_rate | Deleted — do not migrate |
| — | custom_rate_list_custom_custom_rate |
list.custom.custom_rate | Deleted — do not migrate |
| — | custom_rate1_list_custom_custom_rate |
via custom_rates | |
| dependability_score | dependability_scoup_number |
numeric | |
| do_not_book_ids | do_not_book_list_user |
uuid[] FK profiles | |
| — | docs_custom_documents |
custom.documents | Deleted — do not migrate |
| — | docs_list_custom_documents |
via documents.owner_id | Normalized documents rows |
| legacy_document_files | documents_list_file |
text[] | Labeled Documents(delete); migrate only if cutover data requires |
| no_show_count | don_t_show_up_number |
numeric | |
| emergency_contact_first_name | emergency_contact__first_name__text |
text | |
| emergency_contact_phone_number | emergency_contact__phone_number__number |
numeric | |
| first_name | first_name_text |
text | Also stores company display name for role=company (Bubble dual-use) |
| full_name_search | full_name__search__text |
text | Search denorm |
| hr_score | hr_number |
numeric | |
| — | job_list_custom_job |
list.custom.job | Do not migrate — job type residual dead |
| last_name | last_name_text |
text | |
| location | location_geographic_address |
jsonb | Opaque Bubble geographic_address; UI primarily uses city/state |
| no_show_user_ids | no_show_up_list_user |
uuid[] FK profiles | |
| is_online | offline_online_boolean |
boolean | Header presence |
| onboarding_complete | onboarding_boolean |
boolean | |
| phone_number | phone_number_number |
numeric | |
| preferred_company_ids | preferred_list_user |
uuid[] FK profiles | Or join preferred_companies |
| profile_image | profileimage_image |
text | Storage URL/path |
| — | rates_custom_rates |
custom.rates | Deleted — do not migrate |
| — | rates_text |
text | Deleted — do not migrate |
| role | role_option_role |
enum role | contractor|company|admin from option.role db_value |
| grace_delay_job_id | schedule_15_min_delay_text |
text | Scheduled cancel-grace job id |
| active_shift_timer_id | shift_timer_custom_shifts |
uuid FK → shifts | |
| — | shifts_list_custom_shifts |
via shift_contractors | Prefer join table shift_contractors over uuid[] bag |
| skills_summary | short_summary_about_your_experience_text |
text | |
| — | skill_set_filter_list_text |
list.text | Deleted — do not migrate |
| — | skill_set_list_custom_skill_set |
via contractor_skills | |
| — | skill_set_list_option_skill_set |
list.option.skill_set | Deleted — do not migrate |
| state | state_text |
text | CSC API display name |
| stripe_account_id | stripe_account_id_text |
text | MON-MARKETPLACE fence: store/display only; no Connect/KYC/balance until programme decision |
| stripe_name | stripe_name_text |
text | MON-MARKETPLACE fence: same as stripe_account_id |
Also create on every migrated table: id uuid pk, created_at timestamptz, updated_at timestamptz, and for owner-scoped rows created_by uuid when Bubble Created By is load-bearing (messages).
#Option sets → enums
Persist db_value for storage and CHECK/enum members. Keep display strings as UI copy only. Do not invent cleaned members for opaque or typo keys that workflows already write/filter (e.g. rate_filter bTKDr db_value _40_600 while attrs encode min 60 / max 80).
#adminnav (AdminNav)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
shift |
bTHvB0 |
shift |
Shift | |
companies |
bTHvF0 |
companies |
Companies | |
contractors |
bTHvG0 |
contractors |
Contractors | |
payments |
bTHvH0 |
payments |
Payments | |
detailedcompany |
bTHxz0 |
detailedcompany |
DetailedCompany | |
detailedcontractor |
bTICU0 |
detailedcontractor |
DetailedContractor | |
detailedshift |
bTINB0 |
detailedshift |
DetailedShift | |
shift_type |
bTJev |
shift_type |
shift_type | Deleted — omit from enum unless cutover requires; attrs {"deleted":true} |
#admintab (AdminTab)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
veriedcontractors |
bTHvL0 |
veriedcontractors |
VerifiedContractors | |
notverifiedcontractors |
bTHvM0 |
notverifiedcontractors |
NotVerifiedContractors | |
verified_companies |
bTHvN0 |
verified_companies |
VerifiedCompanies | |
notverifiedcompanies |
bTHvR0 |
notverifiedcompanies |
NotVerifiedCompanies | |
openshifts |
bTHvS0 |
openshifts |
OpenShifts | |
activeshifts |
bTHvT0 |
activeshifts |
ActiveShifts | |
all_invoices |
bTKIv0 |
all_invoices |
All invoices | |
upcoming_payment_schedule |
bTKIw0 |
upcoming_payment_schedule |
Upcoming payment schedule | |
previous_payments |
bTKIx0 |
previous_payments |
Previous payments |
#approve_type (Approve_type)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
approved |
bTLhj |
approved |
approved | attrs {"type0":true} |
not_approved |
bTLhn |
not_approved |
not approved | Deleted — omit from enum unless cutover requires; attrs {"deleted":true,"type0":false} |
waiting_for_approval |
bTLho |
waiting_for_approval |
waiting for approval | attrs {"type0":false} |
#break (break)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
10_min |
bTLbD |
10_min |
10 min | attrs {"number":10} |
15_min |
bTLbE |
15_min |
15 min | attrs {"number":15} |
20_min |
bTLbF |
20_min |
20 min | attrs {"number":20} |
25_min |
bTLbJ |
25_min |
25 min | attrs {"number":25} |
30_min |
bTLbK |
30_min |
30 min | attrs {"number":30} |
35_min |
bTLbL |
35_min |
35 min | attrs {"number":35} |
40_min |
bTLbP |
40_min |
40 min | attrs {"number":40} |
45_min |
bTLbQ |
45_min |
45 min | attrs {"number":45} |
50_min |
bTLbR |
50_min |
50 min | attrs {"number":50} |
55_min |
bTLbV |
55_min |
55 min | attrs {"number":55} |
60_min |
bTLbW |
60_min |
60 min | attrs {"number":60} |
#comp_contractors_type (Comp.contractors.type)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
all_contractors |
bTKAD0 |
all_contractors |
All_contractors | |
preferred |
bTKAH0 |
preferred |
Preferred | |
do_not_book |
bTKAI0 |
do_not_book |
Do_not_book |
#docs_type (docs_type)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
diplomas |
bTKzB1 |
diplomas |
Diplomas | |
resume |
bTKzC1 |
resume |
Resume | |
mask_fit_test |
bTKzD1 |
mask_fit_test |
Mask Fit Test | |
driver_s_license |
bTKzH1 |
driver_s_license |
Driver's license | |
hearing_examination_osha |
bTKzI1 |
hearing_examination_osha |
Hearing examination-OSHA | |
references_from_two_foreman_managers |
bTKzJ1 |
references_from_two_foreman_managers |
References from two foreman/managers | |
credentials_detailing_your_ability_to_work_by_the_specialty |
bTKzN1 |
credentials_detailing_your_ability_to_work_by_the_specialty |
Credentials detailing your ability to work by the specialty |
#expierence (Expierence)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
1_year |
bTJKb |
1_year |
1 year | |
2_years |
bTJKf |
2_years |
2 years | |
3_years |
bTJKg |
3_years |
3 years | |
4_years |
bTJKh |
4_years |
4 years | |
5_years |
bTJKl |
5_years |
5 years | |
6_years |
bTJKm |
6_years |
6 years | Deleted — omit from enum unless cutover requires; attrs {"deleted":true} |
#nav (Nav)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
myshifts |
bTHNn |
myshifts |
MyShifts | |
shiftlist |
bTHNo |
shiftlist |
ShiftList | Deleted — omit from enum unless cutover requires; attrs {"deleted":true} |
time_tracking |
bTHNp |
time_tracking |
Time-tracking | |
payments |
bTHNt |
payments |
Payments | |
profile |
bTHQJ |
profile |
Profile | |
contractors |
bTISE |
contractors |
Contractors | |
companies |
bTISF |
companies |
Companies | |
shiftlist0 |
bTISJ |
shiftlist0 |
ShiftList | |
dashboard |
bTISp |
dashboard |
Dashboard | |
company_shifts |
bTISt |
company_shifts |
Company_shifts | |
reports |
bTISu |
reports |
Reports | |
settings |
bTISv |
settings |
Settings |
#navigationindex (NavigationIndex)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
signin |
bTGyz0 |
signin |
SignIn | |
signup |
bTGzA0 |
signup |
SignUp | |
reset |
bTGzB0 |
reset |
Reset | |
verify |
bTHCv0 |
verify |
Verify | attrs {"tab":"Reset"} |
#navigationonboarding (NavigationOnboarding)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
contractor |
bTHGy0 |
contractor |
Contractor | |
company |
bTHGz0 |
company |
Company |
#negatives (Negatives)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
_20_minutes_late |
bTLOy |
_20_minutes_late |
>20 minutes late | attrs {"point":0.5,"__weight":10} |
canceling_outside_of_grace_period__fifteen_minute_window__and__24_hours_of_shift_start_time_ |
bTLOz |
canceling_outside_of_grace_period__fifteen_minute_window__and__24_hours_of_shift_start_time_ |
Canceling outside of grace period (fifteen minute window) and >24 hours of shift start time. | attrs {"point":1,"__weight":20} |
canceling_within_4_hours_of_your_shift_start_time_would_be_considered_a_last_minute_cancellation_and_result_in_a_more_significant_deduction_ |
bTLPD |
canceling_within_4_hours_of_your_shift_start_time_would_be_considered_a_last_minute_cancellation_and_result_in_a_more_significant_deduction_ |
Canceling within 4 hours of your shift start time would be considered a last minute cancellation and result in a more significant deduction. | attrs {"point":1.5,"__weight":30} |
no_show__extreme_deduction_in_dependability_score_ |
bTLPE |
no_show__extreme_deduction_in_dependability_score_ |
No show (extreme deduction in dependability score) | attrs {"point":2,"__weight":40} |
#notification (Notification)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
type0 |
bTLBX |
type0 |
upload_file | attrs {"type0":"has uploaded a new document"} |
delete_file |
bTLBY |
delete_file |
delete_file | attrs {"type0":"deleted the document"} |
#page (Page)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
welcome |
bTIoN |
welcome |
Welcome | |
approve |
bTIov |
approve |
Unapproved |
#pay_practices (Rates)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
concrete_carpenter |
bTHuR1 |
concrete_carpenter |
Concrete Carpenter | |
concrete_walls |
bTHuV1 |
concrete_walls |
Concrete Walls | |
concrete_laborer |
bTHuW1 |
concrete_laborer |
Concrete Laborer | |
concrete_finisher |
bTHuX1 |
concrete_finisher |
Concrete Finisher |
#positives (Positives)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
company_canceled_shift |
bTLPF |
company_canceled_shift |
Company canceled shift | attrs {"point":0.1,"__weight":2} |
completed_shift |
bTLPJ |
completed_shift |
Completed shift | attrs {"point":0.25,"__weight":5} |
company__preferred_you_ |
bTLPK |
company__preferred_you_ |
Company “preferred you” | attrs {"point":0.5,"__weight":10} |
company_messages_tradeforce_with_compliment__would_be_a_manual_increase_ |
bTLPL |
company_messages_tradeforce_with_compliment__would_be_a_manual_increase_ |
Company messages Tradeforce with compliment (would be a manual increase) | attrs {"point":0.75,"__weight":15} |
#rate_filter (Rate_filter)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
_1_20 |
bTKDl |
_1_20 |
$1-20 | attrs {"max0":20,"min0":1} |
_20_40 |
bTKDm |
_20_40 |
$20-40 | attrs {"max0":40,"min0":20} |
_40_60 |
bTKDn |
_40_60 |
$40-60 | attrs {"max0":60,"min0":40} |
_40_600 |
bTKDr |
_40_600 |
$60-80 | attrs {"max0":80,"min0":60} |
_80_100 |
bTKDs |
_80_100 |
$80-100 | attrs {"max0":100,"min0":80} |
#reason_of_cancellation (reason of cancellation)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
sick |
bTKpX |
sick |
Sick | |
family_emergencies |
bTKpY |
family_emergencies |
Family emergencies | |
unexpected_personal_issues |
bTKpZ |
unexpected_personal_issues |
Unexpected personal issues | |
other |
bTKpd |
other |
Other |
#role (Role)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
contractor |
bTHAB0 |
contractor |
Contractor | |
company |
bTHAC0 |
company |
Company | |
admin |
bTIrk |
admin |
Admin |
#shift_type (shift_type)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
posted |
bTJew |
posted |
posted | |
upcoming |
bTJex |
upcoming |
upcoming | |
closed |
bTJfB |
closed |
closed |
#skill_set (Skill set_opt)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
concrete_finisher |
bTJKH |
concrete_finisher |
Concrete Finisher | |
concrete_laborer |
bTJKI |
concrete_laborer |
Concrete Laborer | |
concrete_walls |
bTJKJ |
concrete_walls |
Concrete Walls | |
concrete_carpenter |
bTJKN |
concrete_carpenter |
Concrete Carpenter |
#sort (Sort)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
active_shifts |
bTHRl |
active_shifts |
ActiveShifts | |
closed_shifts |
bTHRp |
closed_shifts |
ClosedShifts | |
detailedview |
bTHVB |
detailedview |
DetailedView | |
posted |
bTIed |
posted |
Posted | |
upcoming |
bTIeh |
upcoming |
Upcoming |
#status_paid (status_paid)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
paid |
bTMpW |
paid |
paid |
#tabs (Tabs)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
upcomingshifts |
bTHRF |
upcomingshifts |
MyShifts | |
detail |
bTHRG |
detail |
DetailShift | |
list |
bTHRH |
list |
List | Deleted — omit from enum unless cutover requires; attrs {"deleted":true} |
payments |
bTHRL |
payments |
Payments | |
shiftlist |
bTHbP |
shiftlist |
ShiftList | |
detailcompany |
bTHfQ |
detailcompany |
DetailCompany | |
closedshifts |
bTHjY |
closedshifts |
ClosedShifts | Deleted — omit from enum unless cutover requires; attrs {"deleted":true} |
postjob |
bTIkP |
postjob |
PostJob | |
unapproved |
bTIpI |
unapproved |
Unapproved | |
welcome |
bTIpJ |
welcome |
Welcome | |
time_tracking |
bTIpy |
time_tracking |
Time-tracking | |
dashboard |
bTIqV |
dashboard |
Dashboard | Deleted — omit from enum unless cutover requires; attrs {"deleted":true} |
upcoming_payment |
bTJCn0 |
upcoming_payment |
Upcoming payment | |
previous_payments |
bTJCo0 |
previous_payments |
Previous payments | |
time_tracking_reports |
bTJPS0 |
time_tracking_reports |
Time-tracking-reports | |
track_time |
bTJPT0 |
track_time |
Track-time | |
dashboard0 |
bTKPl0 |
dashboard0 |
Dashboard | |
filled_hours |
bTKjB4 |
filled_hours |
Filled hours | |
zero_hours |
bTKjF4 |
zero_hours |
Zero hours | |
total_budget_for_the_month |
bTMrh |
total_budget_for_the_month |
Total budget for the month | |
cancellations_no_shows |
bTMrl |
cancellations_no_shows |
Cancellations/No shows | |
tradeforce_usage |
bTMrm |
tradeforce_usage |
TradeForce Usage |
#time_tracking (Time tracking)
No values in export — do not create a Postgres enum. Contractor time-tracking modes use tracking instead.
#tracking (Tracking)
| Target enum member | Bubble id | db_value | Display | Notes |
|---|---|---|---|---|
tracking_your_time |
bTLcl |
tracking_your_time |
Tracking your time | |
time_tracking_reports |
bTLcp |
time_tracking_reports |
Time tracking reports |
#Authorization intent (privacy → RLS)
Bubble privacy roles become Supabase RLS policies. Pattern: everyone denies; named roles grant view_all / search_for to matching actors. One exception, and it is load-bearing: privacy_role:user:everyone carries search_for: true (with view_all: false) — the source lets unauthenticated visitors run searches over User rows. Every other everyone role is all-false. Target default is deny for anon on profiles; keeping any public lookup is a deliberate decision to record, not a rule to copy silently.
| Bubble type | Privacy role | Target RLS intent |
|---|---|---|
chat |
privacy_role:chat:everyone |
deny public / everyone |
chat |
privacy_role:chat:user_ |
authenticated User may select (and search=true) |
custom_rate |
privacy_role:custom_rate:admin_company_ |
authenticated Admin |
custom_rate |
privacy_role:custom_rate:everyone |
deny public / everyone |
custom_rate |
privacy_role:custom_rate:user_ |
authenticated User may select (and search=true) |
documents |
privacy_role:documents:admin_company_ |
authenticated Admin&Company may select (and search=true) |
documents |
privacy_role:documents:everyone |
deny public / everyone |
documents |
privacy_role:documents:user_ |
authenticated User may select (and search=true) |
message |
privacy_role:message:everyone |
deny public / everyone |
message |
privacy_role:message:user_ |
authenticated User may select (and search=true) |
notification |
privacy_role:notification:admin_ |
authenticated User may select (and search=true) |
notification |
privacy_role:notification:everyone |
deny public / everyone |
shifts |
privacy_role:shifts:admin_ |
authenticated User is logged in may select (and search=true) |
shifts |
privacy_role:shifts:everyone |
deny public / everyone |
skill_set |
privacy_role:skill_set:company_ |
authenticated User_skill_set may select (and search=true) |
skill_set |
privacy_role:skill_set:company_admin_ |
authenticated Company |
skill_set |
privacy_role:skill_set:everyone |
deny public / everyone |
time_tracking1 |
privacy_role:time_tracking1:admin_ |
authenticated Admin may select (and search=true) |
time_tracking1 |
privacy_role:time_tracking1:company_ |
authenticated company may select (and search=true) |
time_tracking1 |
privacy_role:time_tracking1:contractor_ |
authenticated contractor may select (and search=true) |
time_tracking1 |
privacy_role:time_tracking1:everyone |
deny public / everyone |
user |
privacy_role:user:admin_company_ |
authenticated Admin |
user |
privacy_role:user:contractor_ |
authenticated User is logged in may select (and search=true) |
user |
privacy_role:user:everyone |
not a plain deny — source grants search_for: true to everyone (view_all: false); target denies anon unless a public lookup is explicitly decided |
user |
privacy_role:user:user__for_company__ |
authenticated User (for company) may select (and search=true) |
#IR evidence
#User types
chat—/user_types/chat(migrate)company—/user_types/company(fold)current_contractors_info—/user_types/current_contractors_info(dead)custom_rate—/user_types/custom_rate(migrate)documents—/user_types/documents(migrate)job—/user_types/job(dead)message—/user_types/message(migrate)notification—/user_types/notification(migrate)rates—/user_types/rates(dead)shifts—/user_types/shifts(migrate)skill_set—/user_types/skill_set(migrate)time_tracking—/user_types/time_tracking(dead)time_tracking1—/user_types/time_tracking1(migrate)user—/user_types/user(migrate)
#Option sets
adminnav—/option_sets/adminnav(8 values)admintab—/option_sets/admintab(9 values)approve_type—/option_sets/approve_type(3 values)break—/option_sets/break(11 values)comp_contractors_type—/option_sets/comp_contractors_type(3 values)docs_type—/option_sets/docs_type(7 values)expierence—/option_sets/expierence(6 values)nav—/option_sets/nav(12 values)navigationindex—/option_sets/navigationindex(4 values)navigationonboarding—/option_sets/navigationonboarding(2 values)negatives—/option_sets/negatives(4 values)notification—/option_sets/notification(2 values)page—/option_sets/page(2 values)pay_practices—/option_sets/pay_practices(4 values)positives—/option_sets/positives(4 values)rate_filter—/option_sets/rate_filter(5 values)reason_of_cancellation—/option_sets/reason_of_cancellation(4 values)role—/option_sets/role(3 values)shift_type—/option_sets/shift_type(3 values)skill_set—/option_sets/skill_set(4 values)sort—/option_sets/sort(5 values)status_paid—/option_sets/status_paid(1 values)tabs—/option_sets/tabs(22 values)time_tracking—/option_sets/time_tracking(0 values)tracking—/option_sets/tracking(2 values)
#Catalog / bundle
- Source preset
bubble-ir, content_hash0730383603a0811b9314f2c6bb54e9b40d7f25e134c9fd1fdbc9d3a68d0a2259 - Inventory
ir/inventory.json(user_type ×14, user_type.field ×185, option_set ×25, option_set.value ×130, privacy_role ×25) - Sanitized export chase:
source/tradeforce-50323.sanitized.bubblepointers/user_types/*,/option_sets/*,/api/bTMBh(send_tracking_approve → deleted custom.time_tracking param; creates use time_tracking1)
#Programme fences
- MON-MARKETPLACE — Stripe Connect/KYC/balance/payouts out of scope; only
stripe_account_id/stripe_namecolumns - MON-CLIENT-WRITES / LOG-CLIENT-WRITES — money and most DB writes need server authority
- LOG-RECURSIVE — rewrite send_tracking_approve against
time_entries, not a second Bubble type port
#Needs human clarification
None. After inventory + sanitized export chase, type dispositions and field mappings are encoded in the export or already decided in module residual dispositions (company folded, job/rates/time_tracking/current_contractors_info dead or empty). Programme fences are scope boundaries, not unanswered schema questions.