# postApi → xapi Batch 5 — Manual Testing Steps

Manual QA steps for every file touched in the Batch 5 postApi → xapi conversion (see `claude_RIO_postApi_replace.md` for the full conversion log). Test on the dev server. For each item: open browser dev tools (Network + Console tabs) before starting so you can confirm the request goes to the right xapi action and returns `success: 1` with no console errors.

General pass/fail criteria for every item below unless noted otherwise:
- Network tab shows a request to `xapi.php` (not `postapi.php`) with the expected action name.
- Response JSON has `result.success = 1` (or the relevant success field) — no `error` key.
- UI reflects the change immediately (list refresh, message banner, closed modal, etc.) without a manual page reload.
- No new console errors/warnings appear.
- A success or error `Message` banner appears as appropriate.

---

## File → Test Tracker

Use this table to push files one at a time (or in their required groups) instead of all at once. Check a file off as you test it on dev, push it to dev's git repo, and eventually migrate it to production. "Ships with" means those files must go together in the same push — they were changed as one feature and testing/deploying only one of the group will not be meaningful (or will break the others).

| File | Test Section | Ships With | Dev Tested | Pushed → Dev Repo | Migrated → Prod |
|---|---|---|---|---|---|
| `utilityFunctions/xapi.js` | [xapi utility](#xapiutility) | Must precede/accompany the **first** file you push below — every other file depends on it | ☐ | ☐ | ☐ |
| `components/Calendars/actions.js` | [Calendars](#calendars) | — | ☐ | ☐ | ☐ |
| `components/Chatbot/Chatbot.js` | [Chatbot](#chatbot) | — | ☐ | ☐ | ☐ |
| `containers/DocumentChecklist/actions.js` | [DocumentChecklist](#documentchecklist) | — | ☐ | ☐ | ☐ |
| `containers/AgentStaffSettings/AgentStaffSettings.js` | [AgentStaffSettings](#agentstaffsettings) | — | ☐ | ☐ | ☐ |
| `containers/CRM/actions.js` | [CRM](#crm) | `containers/CRM/reducer.js` | ☐ | ☐ | ☐ |
| `containers/CRM/reducer.js` | [CRM](#crm) | `containers/CRM/actions.js` | ☐ | ☐ | ☐ |
| `containers/CRMContactHistoryList/actions.js` | [CRMContactHistoryList](#crmcontacthistorylist) | — | ☐ | ☐ | ☐ |
| `containers/Camera/Camera.js` | [Camera](#camera) | `containers/Camera/CameraPropertyAlbum.js` | ☐ | ☐ | ☐ |
| `containers/Camera/CameraPropertyAlbum.js` | [Camera](#camera) | `containers/Camera/Camera.js` | ☐ | ☐ | ☐ |
| `containers/Campaigns/actions.js` | [Campaigns](#campaigns) | — | ☐ | ☐ | ☐ |
| `containers/Clients/Clients.js` | [Clients](#clients) | — | ☐ | ☐ | ☐ |
| `containers/CompanyCrmTags/actions.js` | [CompanyCrmTags](#companycrmtags) | — | ☐ | ☐ | ☐ |
| `containers/CrmContactTabs/ContactTabAddForm.js` | [CrmContactTabs](#crmcontacttabs) | other 3 CrmContactTabs files | ☐ | ☐ | ☐ |
| `containers/CrmContactTabs/ContactTabCFForm.js` | [CrmContactTabs](#crmcontacttabs) | other 3 CrmContactTabs files | ☐ | ☐ | ☐ |
| `containers/CrmContactTabs/ContactTabEditForm.js` | [CrmContactTabs](#crmcontacttabs) | other 3 CrmContactTabs files | ☐ | ☐ | ☐ |
| `containers/CrmContactTabs/CrmContactTabOverview.js` | [CrmContactTabs](#crmcontacttabs) | other 3 CrmContactTabs files | ☐ | ☐ | ☐ |
| `containers/DashboardPropertyList/actions.js` | [DashboardPropertyList](#dashboardpropertylist) | — (dead import removal only) | ☐ | ☐ | ☐ |
| `containers/DetailsTab/actions.js` | [DetailsTab](#detailstab) | — | ☐ | ☐ | ☐ |
| `containers/DocumentComplianceSettings/actions.js` | [DocumentComplianceSettings](#documentcompliancesettings) | — | ☐ | ☐ | ☐ |
| `containers/EmailTemplates/actions.js` | [EmailTemplates](#emailtemplates) | — | ☐ | ☐ | ☐ |
| `containers/FinancialTab/actions.js` | [FinancialTab](#financialtab) | — | ☐ | ☐ | ☐ |
| `containers/FutureProjection/actions.js` | [FutureProjection](#futureprojection) | — (dead import removal only) | ☐ | ☐ | ☐ |
| `containers/LeadCapture/actions.js` | [LeadCapture](#leadcapture) | — | ☐ | ☐ | ☐ |
| `containers/MyProfile/actions.js` | [MyProfile](#myprofile) + [HIGH PRIORITY](#high-priority) | `containers/MyProfile/QBConnector.js` | ☐ | ☐ | ☐ |
| `containers/MyProfile/QBConnector.js` | [MyProfile](#myprofile) | `containers/MyProfile/actions.js` | ☐ | ☐ | ☐ |
| `containers/OfferEmailTemplate/actions.js` | [OfferEmailTemplate](#offeremailtemplate) | — | ☐ | ☐ | ☐ |
| `containers/OfficesAndTeams/actions.js` | [OfficesAndTeams](#officesandteams) | — | ☐ | ☐ | ☐ |
| `containers/OpenHouseList/actions.js` | [OpenHouseList](#openhouselist) | — | ☐ | ☐ | ☐ |
| `containers/OtherIncomeList/actions.js` | [OtherIncomeList](#otherincomelist) | — | ☐ | ☐ | ☐ |
| `containers/Projection/actions.js` | [Projection](#projection) | — | ☐ | ☐ | ☐ |
| `containers/Property/actions.js` | [Property](#property) + [HIGH PRIORITY](#high-priority) | — | ☐ | ☐ | ☐ |
| `containers/PropertyAttributes/actions.js` | [PropertyAttributes](#propertyattributes) | — | ☐ | ☐ | ☐ |
| `containers/PropertyHistoryList/actions.js` | [PropertyHistoryList](#propertyhistorylist) | — (dead import removal only) | ☐ | ☐ | ☐ |
| `containers/PropertyList/actions.js` | [PropertyList](#propertylist) | — | ☐ | ☐ | ☐ |
| `containers/RecruitingSettings/actions.js` | [RecruitingSettings](#recruitingsettings) | — | ☐ | ☐ | ☐ |
| `containers/Reporting/Report.js` | [Reporting](#reporting) | `containers/Reporting/ReportControls.js` | ☐ | ☐ | ☐ |
| `containers/Reporting/ReportControls.js` | [Reporting](#reporting) | `containers/Reporting/Report.js` | ☐ | ☐ | ☐ |
| `containers/Roles/actions.js` | [Roles](#roles) | — | ☐ | ☐ | ☐ |
| `containers/Support/Support.js` | [Support](#support) | — | ☐ | ☐ | ☐ |
| `containers/Tasks/Tasks.js` | [Tasks](#tasks) | — | ☐ | ☐ | ☐ |
| `containers/TextMessage/actions.js` | [TextMessage](#textmessage) | — | ☐ | ☐ | ☐ |
| `containers/Users/actions.js` | [Users](#users) | — | ☐ | ☐ | ☐ |
| `containers/VaultList/VaultDetails.js` | [VaultList](#vaultlist) | `containers/VaultList/actions.js` | ☐ | ☐ | ☐ |
| `containers/VaultList/actions.js` | [VaultList](#vaultlist) | `containers/VaultList/VaultDetails.js` | ☐ | ☐ | ☐ |
| `containers/VendorNetwork/actions.js` | [VendorNetwork](#vendornetwork) | — | ☐ | ☐ | ☐ |
| `containers/Workflow/actions.js` | [Workflow](#workflow) | — | ☐ | ☐ | ☐ |

Not app code — no testing needed, push whenever convenient:
- `../claude_RIO_React.md` (documentation only)

**Suggested order:** push `utilityFunctions/xapi.js` together with whichever file group you test first (it has no independent UI to test — its correctness is proven by every other file's test passing). After that, work down the table in any order; each remaining row (or ship-with group) is independent of the others, so you can dev-test → push → migrate one at a time. Do the two [HIGH PRIORITY](#high-priority) rows (`containers/Property/actions.js`, `containers/MyProfile/actions.js`) with extra care since they touch real payments.

---

<a id="high-priority"></a>
## ⚠️ HIGH PRIORITY — Real Credit Card Charges

Do these two first, and only with a test/sandbox card. Confirm with whoever owns the payment gateway which test card number to use before charging anything.

### 1. Vault payment flow — `containers/Property/actions.js`
Functions: `xapiVaultPaymentFieldsGet`, `xapiProcessVaultPayment`, `xapiVaultArchiveSet`

1. Open a property that has an item in the vault (or add one to trigger the initial archive flow).
2. **Initial archive**: Archive a property to the vault for the first time. Confirm `xapiVaultArchiveSet` fires and the property shows as archived/in-vault afterward.
3. **Extend storage (`action=upgrade`)**: From the Vault list, choose to extend/upgrade storage on an archived property. Confirm the payment fields form loads (`xapiVaultPaymentFieldsGet`), enter a test card, submit.
   - Confirm `xapiProcessVaultPayment` request/response in Network tab.
   - Confirm success message and that the vault expiration/status updates in the UI.
4. **Restore from vault**: Restore a property from the vault using a test card.
   - Confirm the restore payment succeeds and the property leaves vault/archived state.
5. Try a **declined test card** if your gateway has one, and confirm the failure path shows a clear error message and does not falsely mark the property as paid/restored.

### 2. General company payment/billing — `containers/MyProfile/actions.js`
Function: general payment processing action (billing/subscription charge)

1. Go to My Profile → Billing/Payment section.
2. Submit a payment with a test card.
3. Confirm the charge request goes through xapi, returns success, and the UI (invoice/receipt/plan status) updates.
4. Try a declined test card and confirm the error is surfaced to the user, not swallowed.

---

<a id="calendars"></a>
## Calendars — `components/Calendars/actions.js`
Functions: `xapiGetCalenderEventList`, `xapiAddEvent`, `xapiUpdateEvent`

1. Open the Calendar view — confirm the event list loads (`calendareventlist_get`).
2. Add a new event with a title, date, and time. Confirm it appears on the calendar immediately.
3. Edit an existing event (change date/time or description). Confirm the change persists after a refresh.
4. If your test user has Google or Outlook calendar sync linked, confirm adding/editing an event triggers the auto-sync call (check Network tab for `calendargoogle_syncevents` / `calendaroutlook_syncevents` firing after the add/update).
5. Delete an event and confirm it disappears (unchanged code path, but verify no regression).

<a id="chatbot"></a>
## Chatbot — `components/Chatbot/Chatbot.js`
Function: `dynamicSlotPost` (dynamic `bot_*` action)

1. Open the Chatbot widget and try each supported prompt:
   - Property list
   - Commission graph
   - Closing properties
   - Projections
   - Task list
2. Confirm each returns a valid response with no console errors and the correct `bot_*` xapi action fires in the Network tab.

<a id="documentchecklist"></a>
## DocumentChecklist — `containers/DocumentChecklist/actions.js`
Functions: checklist add/update, checklist-item add/update

1. Open a property's Document Checklist tab.
2. Add a new checklist. Confirm it appears in the list.
3. Update the checklist's name/settings. Confirm the change saves.
4. Add a checklist item to a checklist. Confirm it appears under the correct checklist.
5. Update/check off a checklist item. Confirm the status persists after refresh.

<a id="agentstaffsettings"></a>
## AgentStaffSettings — `containers/AgentStaffSettings/AgentStaffSettings.js`
Function: `xapiAgentStaffSettingsUpdate`

1. Go to an agent/staff member's settings (email settings area).
2. Change a setting (e.g., an email notification toggle) and save.
3. Refresh the page and confirm the change persisted.

<a id="crm"></a>
## CRM — `containers/CRM/actions.js`, `containers/CRM/reducer.js`
(Converted in Batch 3, file still shows as locally modified — re-verify core flows.)

1. Add a new CRM contact — confirm it appears in the contact list.
2. Edit an existing contact's fields and save.
3. Delete a contact and confirm it's removed from the list.
4. Add an appointment and a note to a contact; confirm both save and display correctly.
5. Add a relation (e.g., friend/family) to a contact; confirm it displays with correct Title Case labels (not lowercase, not "null" for blank fields).
6. Run an export of selected contacts; confirm the file downloads with correct data.
7. Run the full import flow (upload → validate → finalize) with a small test file; confirm validation errors display correctly for bad rows and valid rows import successfully.

<a id="crmcontacthistorylist"></a>
## CRMContactHistoryList — `containers/CRMContactHistoryList/actions.js`
No live `postApi` call was present (dead import only, removed). Sanity check:

1. Open a contact's history tab and confirm it still loads without console errors.

<a id="camera"></a>
## Camera — `containers/Camera/Camera.js`, `containers/Camera/CameraPropertyAlbum.js`
Function: `photoAdd` (with new recursive `addNestedParams` helper for multi-upload)

1. From a mobile or mobile-emulated view, use the phone camera capture flow to take/upload a single photo to a property album.
2. Confirm the photo appears in the album after upload.
3. If a multi-photo upload path is reachable (batch capture), upload 2–3 photos at once and confirm all of them save (this exercises the new nested-params helper — this is the part most likely to regress).
4. `CameraPropertyAlbum.js` needed no functional conversion — just confirm the album view still opens/loads normally.

<a id="campaigns"></a>
## Campaigns — `containers/Campaigns/actions.js`
Functions: campaign add/update, action inactivate/add, category add

1. Create a new campaign, including at least one tag. Confirm it saves and appears in the campaign list.
2. Edit an existing campaign's details and save.
3. Add an action to a campaign; confirm it appears under that campaign.
4. Inactivate a campaign action; confirm it's marked inactive (not deleted) in the UI.
5. Add a new campaign category; confirm it's selectable when creating/editing a campaign.

<a id="clients"></a>
## Clients — `containers/Clients/Clients.js`
Functions: `crmContactClientAdd`, `crmContactClientUpdate`

1. Add a new client. Confirm it appears in the client list.
2. Edit an existing client's fields and save. Confirm the changes persist after refresh.

<a id="companycrmtags"></a>
## CompanyCrmTags — `containers/CompanyCrmTags/actions.js`
Functions: tag add/edit

1. Go to company CRM tag settings. Add a new tag; confirm it appears in the list.
2. Edit an existing tag's name/color; confirm the change saves and reflects wherever tags are shown (e.g., on CRM contacts).

<a id="crmcontacttabs"></a>
## CrmContactTabs — `ContactTabAddForm.js`, `ContactTabCFForm.js`, `ContactTabEditForm.js`, `CrmContactTabOverview.js`
Functions: tab add, custom-field add/update, tab update

1. On a CRM contact, add a new custom tab. Confirm it appears in the tab list.
2. Edit an existing tab's name/settings; confirm it saves.
3. Add a custom field to a tab; confirm it appears and is editable on the contact.
4. Update an existing custom field's value; confirm it persists after refresh.

<a id="dashboardpropertylist"></a>
## DashboardPropertyList — `containers/DashboardPropertyList/actions.js`
No live `postApi` call was present (dead import only, removed). Sanity check:

1. Open the dashboard property list and confirm it still loads/filters normally.

<a id="detailstab"></a>
## DetailsTab — `containers/DetailsTab/actions.js`
Functions: document update, field update, MAT options get, MAT options run

1. On a property's Details tab, edit a field (e.g., an Overview or Listing field) and confirm it saves without the "disappearing form" loading-bar regression (see the earlier Loading Bar fix — form should stay visible while saving).
2. Upload/update a document reference tied to Details (document update path) — confirm it saves.
3. Open MAT options for a field type; confirm the options list loads.
4. Run a MAT option action; confirm it executes and returns a result.

<a id="documentcompliancesettings"></a>
## DocumentComplianceSettings — `containers/DocumentComplianceSettings/actions.js`
Function: settings save

1. Go to Document Compliance Settings. Change a setting and save.
2. Refresh and confirm the change persisted.

<a id="emailtemplates"></a>
## EmailTemplates — `containers/EmailTemplates/actions.js`
Functions: template add/update/remove, category add

1. Add a new email template. Confirm it appears in the template list.
2. Edit an existing template's subject/body and save.
3. **Remove a template** — this function was previously hardcoded to a dead dev URL (`http://dev.feb.rio.matraex.com/xapi.php`); specifically confirm the delete now actually works (request goes to the current domain's xapi endpoint) and the template disappears from the list.
4. Add a new template category; confirm it's selectable when creating/editing a template.

<a id="financialtab"></a>
## FinancialTab — `containers/FinancialTab/actions.js`
Functions: financial state save, CDA send, CDA buyer add

1. On a property's Financial tab, edit and save the financial state/figures.
2. Send a CDA (Closing Disclosure/Agreement) — confirm the send action completes and any confirmation message appears.
3. Add a CDA buyer; confirm it appears in the CDA buyer list.

<a id="futureprojection"></a>
## FutureProjection — `containers/FutureProjection/actions.js`
No live `postApi` call was present (dead import only, removed). Sanity check:

1. Open the Future Projection view and confirm it still loads/calculates normally.

<a id="leadcapture"></a>
## LeadCapture — `containers/LeadCapture/actions.js`
Not documented in the Batch 5 completion log — verify no unrelated regression:

1. Submit a test lead through the Lead Capture form.
2. Confirm the lead is created and appears wherever new leads are surfaced (CRM/lead list), and no console errors appear on submit.

<a id="myprofile"></a>
## MyProfile — `containers/MyProfile/actions.js`, `containers/MyProfile/QBConnector.js`
Functions: profile update, profile document add/edit, general payment (see [HIGH PRIORITY](#high-priority) above), QuickBooks connector update

1. Update a profile field (name, phone, etc.) and save; confirm it persists.
2. Add a profile document; confirm it appears in the profile documents list.
3. Edit an existing profile document's metadata; confirm the change saves.
4. Update the QuickBooks connector settings/connection; confirm the save completes and connection status displays correctly.
5. (Dead code note: `xapiProfileDocumentCategoryAdd` points at a nonexistent endpoint and is intentionally left on `postApi` — no need to test; it's not wired to a reachable UI action, but if you do find a UI path to it, confirm it fails gracefully rather than crashing.)

<a id="offeremailtemplate"></a>
## OfferEmailTemplate — `containers/OfferEmailTemplate/actions.js`
Function: template update

1. Edit the offer email template's subject/body and save.
2. Confirm the change is reflected the next time an offer notification is sent/previewed.

<a id="officesandteams"></a>
## OfficesAndTeams — `containers/OfficesAndTeams/actions.js`
Functions: office add/update, team inactivate/update/add, staff remove

1. Add a new office; confirm it appears in the office list.
2. Edit an existing office's details and save.
3. Add a new team; confirm it appears under the correct office.
4. Edit a team's details and save.
5. Inactivate a team; confirm it's marked inactive (not deleted).
6. Remove a staff member from a team; confirm they no longer appear on that team's roster.

<a id="openhouselist"></a>
## OpenHouseList — `containers/OpenHouseList/actions.js`
Functions: open house add/update

1. Add a new open house event to a property; confirm it appears in the list.
2. Edit an existing open house's date/time/details; confirm the change saves.

<a id="otherincomelist"></a>
## OtherIncomeList — `containers/OtherIncomeList/actions.js`
Functions: other income add/update

1. Add a new "other income" entry; confirm it appears in the list and totals update if applicable.
2. Edit an existing entry; confirm the change saves.

<a id="projection"></a>
## Projection — `containers/Projection/actions.js`
Function: `generateChart` (`bot_mycommissiongraph_get`)

1. Open the Projection view and generate the commission graph chart.
2. Confirm the chart renders with data and no console errors.

<a id="property"></a>
## Property — `containers/Property/actions.js` (bonus find, non-payment functions)
Functions: photo add/remove/multi-remove/zip/edit/addendum, social media post, property assignment add, vendor list search

1. **Photo add** — upload a single new photo to a property; confirm it appears in the photo list/album.
2. **Photo remove (single)** — delete one photo; confirm it's removed from the list.
3. **Photo multi-remove** — select multiple photos and delete them at once; confirm all selected photos are removed (exercises the new `addNestedParams` helper).
4. **Photo zip** — download a zip of a photo group; confirm the zip downloads and contains the expected photos.
5. **Photo edit** — edit a photo's showcase flag, title, and description; confirm changes save and display correctly.
6. **Photo addendum** — create a photo addendum; confirm it generates/saves correctly.
7. **Social media post** — post a property to social media; confirm the post action completes successfully (check for the expected confirmation, not just no errors — verify on the actual social platform if credentials are available in dev).
8. **Property assignment add** — assign a vendor/agent to a property; confirm the assignment appears.
9. **Vendor list search** — search the assignee vendor list with at least one filter applied; confirm filtered results return correctly (exercises the nested `form.filterby` params).

(Also re-verify the two documented dead-code spots don't interfere: the commented-out duplicate `xapiOfferFieldUpdate` block and any leftover references — should be inert.)

Note: this file also contains the [HIGH PRIORITY](#high-priority) vault payment functions — test both sections since they're the same file.

<a id="propertyattributes"></a>
## PropertyAttributes — `containers/PropertyAttributes/actions.js`
Functions: assignment update, type options get, attribute delete

1. Assign a property attribute to a broker type; confirm it saves.
2. Unassign it; confirm it's removed.
3. Open "Add Attribute" and select each field type; confirm the type-specific options fields load correctly for each type.
4. Delete a custom attribute; confirm it's removed from the list.

<a id="propertyhistorylist"></a>
## PropertyHistoryList — `containers/PropertyHistoryList/actions.js`
No live `postApi` call was present (dead import only, removed). Sanity check:

1. Open a property's History tab and confirm the historical data table still loads.

<a id="propertylist"></a>
## PropertyList — `containers/PropertyList/actions.js`
Functions: export/import flow, add property, MAT options (file upload `propertyUploadImport` intentionally still on `postApi` — not in scope here)

1. Export selected properties; confirm the file downloads with correct data.
2. Run the full property import flow: upload a test file → validate → finalize. Confirm validation errors display correctly for bad rows, and valid rows import successfully.
3. Add a new property, including MAT options fields; confirm it saves and appears in the property list.
4. Confirm validation errors (e.g., missing required fields) still display correctly on the add-property form.

<a id="recruitingsettings"></a>
## RecruitingSettings — `containers/RecruitingSettings/actions.js`
Function: settings save

1. Change a recruiting setting and save.
2. Refresh and confirm the change persisted.

<a id="reporting"></a>
## Reporting — `containers/Reporting/Report.js`, `containers/Reporting/ReportControls.js`
Functions: performance get/export, report export/run, memo save

1. Run a standard report; confirm results display.
2. Export a report; confirm the file downloads with correct data.
3. Save a customized/memorized report (memo save); confirm it's saved and can be reloaded later.
4. Run the Performance report view; confirm it loads data correctly.
5. Export the Performance report; confirm the export downloads correctly.

<a id="roles"></a>
## Roles — `containers/Roles/actions.js`
Functions: permission update, HOA add/update/document-add, note update

1. Edit a role's permissions and save; confirm the change takes effect (e.g., a user in that role gains/loses access to the expected feature).
2. Add a new HOA record; confirm it appears in the HOA list.
3. Edit an existing HOA record and save.
4. Attach a document to an HOA record; confirm it appears in the HOA's documents.
5. Update a generic note (via Notes.js / NotesOverview.js); confirm the change persists.

<a id="support"></a>
## Support — `containers/Support/Support.js`
Function: `analyticPost` (fire-and-forget analytics logging)

1. Open a support video and a support document from the Support page.
2. Confirm both open with no console errors (the analytics call is fire-and-forget, so it shouldn't block or break playback/viewing even if you can't directly observe its success).

<a id="tasks"></a>
## Tasks — `containers/Tasks/Tasks.js`
Function: `task_add` (also has a dead duplicate `task_add` call commented out — should be inert)

1. From the Tasks list page, add a new task using the on-page add form (not the AddTask modal elsewhere in the app).
2. Confirm the task appears in the list immediately without a manual refresh.

<a id="textmessage"></a>
## TextMessage — `containers/TextMessage/actions.js`
Function: `xapiTextMessagePost`

1. Send a text message to a test contact/number.
2. Confirm the message send request completes and the message history/thread refreshes to show the sent message.

<a id="users"></a>
## Users — `containers/Users/actions.js`
Functions: permission update, add/save user, import fields save/validate/finalize (file upload `xapiUserUploadImport` intentionally still on `postApi`; 5 dead orphaned functions intentionally left on `postApi`)

1. Add a new user, including a profile photo. Confirm the user is created and the photo displays.
2. Add a new user **without** a profile photo. Confirm it still saves correctly.
3. Edit an existing user's details and save.
4. Update a user's permission group; confirm the new permissions take effect for that user.
5. Run the full user import flow: upload a test file → validate → finalize. Confirm validation errors display for bad rows and valid rows import successfully.
6. (No need to test the 5 dead orphaned functions — confirm they're not reachable from any routed page, only from the unrouted `TestComponent.js`.)

<a id="vaultlist"></a>
## VaultList — `containers/VaultList/VaultDetails.js`, `containers/VaultList/actions.js`
Mostly dead code (`xapiVaultUpdate` / `editVault()` point at a nonexistent `vault_update` endpoint and aren't wired to any UI). The real vault flow lives in Property/actions.js (see [HIGH PRIORITY](#high-priority) section above).

1. Open the Vault List page and confirm it loads and displays vaulted properties correctly.
2. Open Vault Details for a property; confirm the detail view loads without errors.
3. Confirm there is no reachable "Edit Vault" UI action that would hit the dead `vault_update` endpoint (if one exists, flag it — it will fail).

<a id="vendornetwork"></a>
## VendorNetwork — `containers/VendorNetwork/actions.js`
Functions: vendor invite validate/send

1. Enter a vendor's email/info to send an invite; confirm validation runs (e.g., catches a malformed email) before sending.
2. Send a valid vendor invite; confirm the invite is sent and the vendor appears in a "pending" or "invited" state.

<a id="workflow"></a>
## Workflow — `containers/Workflow/actions.js`
Functions: trigger select-update, add, remove

1. Add a new master task trigger/workflow; confirm it appears in the list.
2. Edit a trigger's select/staff assignment field; confirm the change saves.
3. Remove a trigger; confirm it's removed from the list.

<a id="xapiutility"></a>
## xapi utility — `utilityFunctions/xapi.js`
This is the shared utility every conversion above depends on. If anything changed here beyond what the batches needed:

1. Spot-check one simple GET-style xapi call (e.g., loading any list page) and one call with bracket-notation nested params (e.g., Calendar add event or Campaign add) to confirm both still serialize correctly.
2. Confirm auth (`loginauth`) is still attached correctly to every request — check the Network tab payload on any call.

---

## After Testing

For each item that fails, note:
- The exact xapi action name from the Network tab request.
- The response body (especially any `error` key).
- Whether it's a regression from `postApi` behavior or a pre-existing issue surfaced by the conversion.

Report failures back before promoting a file (or file group) further (dev server check-in → migration to production per project git workflow). Since each row/group in the tracker above is independent of the others (aside from the noted "Ships With" pairs and the shared `xapi.js` utility), you can move a single file from dev-tested → pushed → migrated without waiting on the rest of Batch 5.
