---
name: sf-scenario-test
description: Run scenario-based Salesforce compatibility tests using A/B comparison between Salesforce (oracle) and NexusCRM (under test). Supports API tests (go test), Chrome E2E tests (claude --chrome), and Chrome A/B comparison. Use when the user wants to verify fixes, run regression tests, or validate Salesforce compatibility.
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, Agent
---

# Salesforce Scenario Test Skill

Run scenario-based compatibility tests from `tests/contract/scenarios/`.

The core methodology is **A/B testing**: run the same frontend against two backends
(Salesforce sandbox = oracle, NexusCRM = under test) and compare behavior. Any
difference in the NexusCRM backend is a confirmed bug; any difference in both
backends is a frontend or configuration issue.

---

## Architecture

```
                  +-----------------------+
                  |  Chrome (localhost)   |
                  |  Frontend on :5173    |
                  +----------+------------+
                             |
                     Vite proxy (/services/*)
                             |
              .env controls target
                  /                \
                 v                  v
+-------------------------------+  +-------------------------------------------+
| NexusCRM Backend              |  | Salesforce Sandbox (Oracle)               |
| https://nexuscrm.pingcap.com  |  | pingcap--partialcop.sandbox.my.salesforce |
+-------------------------------+  +-------------------------------------------+
```

**Key insight**: For proxy-based A/B switching, run the frontend on `localhost`.
The deployed app is available at `https://nexuscrm.pingcap.com`, but when you
need Vite to proxy requests between NexusCRM and Salesforce you should still use
`localhost:5173`. The old HTTP deployment at `http://34.59.60.122:8079` is
obsolete and should not be used.

---

## Scenario Registry

All scenarios are defined in `tests/contract/scenarios/scenarios.yaml`. Each has:
- `id` -- unique identifier (e.g., VAL-001, FLOW-001)
- `name` -- human-readable description
- `type` -- `api` (go test), `chrome` (browser), or `both`
- `test_file` -- path to test implementation

---

## Mode 1: API Scenarios (go test)

### Run all
```bash
cd tests/contract
CONTRACT_TARGET=local CONTRACT_BASE_URL=https://nexuscrm.pingcap.com \
  go test -v -count=1 ./scenarios/api/... -timeout 300s
```

### Run single
```bash
cd tests/contract
CONTRACT_TARGET=local CONTRACT_BASE_URL=https://nexuscrm.pingcap.com \
  go test -v -run "TestVAL001" ./scenarios/api/... -timeout 120s
```

### API A/B comparison
```bash
# Oracle baseline (Salesforce sandbox)
cd tests/contract
CONTRACT_TARGET=sandbox \
  go test -v -count=1 ./scenarios/api/... -timeout 300s 2>&1 | tee /tmp/sf.log

# NexusCRM under test
CONTRACT_TARGET=local CONTRACT_BASE_URL=https://nexuscrm.pingcap.com \
  go test -v -count=1 ./scenarios/api/... -timeout 300s 2>&1 | tee /tmp/nexus.log

# Compare pass/fail status
diff \
  <(grep -E "^--- (PASS|FAIL|SKIP):" /tmp/sf.log | sort) \
  <(grep -E "^--- (PASS|FAIL|SKIP):" /tmp/nexus.log | sort)
```

---

## Mode 2: Chrome Scenarios -- NexusCRM Backend

Chrome scenarios require `claude --chrome` with the Claude in Chrome extension.

### Prerequisites
1. Claude Code started with `claude --chrome`
2. Chrome extension connected (verify with `/chrome`)
3. Frontend source available at `frontend/`

### Step 1: Start local frontend pointing to NexusCRM

```bash
# Write .env (this controls the Vite proxy target)
echo "REACT_APP_BACKEND_URL=https://nexuscrm.pingcap.com" > frontend/.env

# Kill any existing Vite process, then start fresh
pkill -f vite || true
cd frontend && npm run dev &
# Wait for "ready in XXXms" message -- frontend is on http://localhost:5173
```

### Step 2: Acquire NexusCRM token

```bash
TOKEN=$(curl -s -X POST "https://nexuscrm.pingcap.com/services/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=test-gosf-client&client_secret=test-gosf-secret-123" \
  | python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))")
echo "$TOKEN"
```

### Step 3: Login via localStorage injection

Navigate to `http://localhost:5173`, then run in the browser console:
```javascript
localStorage.clear();
localStorage.setItem('auth_token', '<TOKEN>');
localStorage.setItem('instance_url', 'https://nexuscrm.pingcap.com');
location.reload();
```

**Verify**: Main dashboard loads with "SA" user avatar and "Salesforce" badge.

### Step 4: Execute scenarios

For each `type: chrome` scenario in `scenarios.yaml`:
1. Read the `.md` file for the scenario
2. Follow the **Steps** section exactly
3. Compare actual result with expected result
4. Check failure indicators
5. Take a screenshot for evidence

---

## Mode 3: Chrome Scenarios -- Salesforce Backend (Oracle)

This mode runs the same frontend against the real Salesforce sandbox. Used as the
oracle baseline for A/B comparison.

### Step 1: Start local frontend pointing to Salesforce

```bash
echo "REACT_APP_BACKEND_URL=https://pingcap--partialcop.sandbox.my.salesforce.com" > frontend/.env

pkill -f vite || true
cd frontend && npm run dev &
```

### Step 2: Acquire Salesforce access token

```bash
SF_TOKEN=$(curl -s -X POST \
  https://pingcap--partialcop.sandbox.my.salesforce.com/services/oauth2/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=client_credentials' \
  --data-urlencode 'client_id=3MVG96vIeT8jJWjJmgzR59tsgAKUd1JtyGnX.Ljc7tLeqVcpNlm6mmdtnkn._7uOKTDwrUu6xrY7SQUUZXPfk' \
  --data-urlencode 'client_secret=622093F9567636D20B9078B3B960B6EA8FFAC97758326A414D7473508C872748' \
  | python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))")
echo "$SF_TOKEN"
```

### Step 3: Login via Vite proxy method (CRITICAL -- do NOT use direct SF connection)

Navigate to `http://localhost:5173`. Use the **Backend mode** login:

1. Click "Backend" login tab
2. Open "Advanced Options"
3. Enter:
   - **Token**: the SF access token (starts with `00D...`)
   - **Instance URL**: `http://localhost:5173`  <-- THIS IS THE KEY
4. Click Connect

**WHY `localhost:5173` for instance_url?** The frontend uses `instance_url` to decide
where to send API requests. If you set it to `salesforce.com`, the frontend will
try direct cross-origin requests to Salesforce, which fails due to CORS. By setting
it to `localhost:5173`, all requests go through the Vite proxy, which forwards them
to whatever `REACT_APP_BACKEND_URL` points to (in this case, the SF sandbox).

**DO NOT** use the alternative approach of injecting localStorage with
`instance_url = salesforce.com` -- this triggers "direct mode" in the frontend's
API client, bypassing the Vite proxy, and every request will fail with CORS errors
or "Object not found" errors.

---

## Mode 4: Chrome A/B Comparison

This is the most powerful mode. Run the same scenario against both backends and
compare the results directly.

### Full A/B switching procedure

1. **Stop Vite**: `pkill -f vite`
2. **Change `.env`** to point to target backend
3. **Restart Vite**: `cd frontend && npm run dev &`
4. **Clear localStorage** in browser: `localStorage.clear()`
5. **Login** with appropriate token (NexusCRM JWT or SF access token)
6. **Navigate** to the test page
7. **Record results** (screenshot + observation)
8. **Repeat** for the other backend

### Interpreting A/B results

| SF Result | NexusCRM Result | Conclusion |
|-----------|----------------|------------|
| Works correctly | Different behavior | Confirmed backend bug in NexusCRM |
| Same behavior | Same behavior | Not a backend bug (frontend or config) |
| SF has error too | NexusCRM has error | Not a NexusCRM-specific bug |
| Works | Missing feature | Unfinished NexusCRM feature |

---

## Verified A/B Results (Baseline -- 2026-03-25)

These results were obtained by running Chrome scenarios against both backends.

| Scenario | SF Backend | NexusCRM Backend | Confirmed Bug? |
|----------|-----------|-----------------|---------------|
| FLOW-002 | Canvas complete, all nodes visible | Assignment node missing | YES -- backend |
| VAL-004 | "Website must be blank..." (friendly) | "unknown name NOT (1:1)" (internal) | YES -- backend |
| DATA-001 | "Account Name" (Label) | "Name" (ApiName variant) | YES -- backend |
| DATA-002 | localStorage (SELECTED 0) | localStorage (SELECTED 0) | NO -- frontend issue |
| DATA-003 | SYSTEM-only sidebar | SYSTEM-only sidebar | NO -- App config |
| APPR-002 | No pending data to compare | All "Unknown" | Reproduced, no A/B baseline |

---

## Test Data Management for SF Sandbox

Some Chrome scenarios require test data on the Salesforce sandbox (e.g., validation
rules). Use the SF Tooling API to create, test, and clean up.

### Example: Create a validation rule for VAL-004

```bash
# Create validation rule on Account
curl -s -X POST \
  "https://pingcap--partialcop.sandbox.my.salesforce.com/services/data/v63.0/tooling/sobjects/ValidationRule" \
  -H "Authorization: Bearer $SF_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "EntityDefinitionId": "Account",
    "Metadata": {
      "active": true,
      "description": "Test rule for VAL-004",
      "errorConditionFormula": "NOT(ISBLANK(Website))",
      "errorMessage": "Website must be blank for this test"
    },
    "FullName": "Account.Test_VAL004_Rule"
  }'
# Save the returned Id for cleanup
```

### Cleanup after testing

```bash
curl -s -X DELETE \
  "https://pingcap--partialcop.sandbox.my.salesforce.com/services/data/v63.0/tooling/sobjects/ValidationRule/<ID>" \
  -H "Authorization: Bearer $SF_TOKEN"
```

**Rule**: Always clean up test data on the SF sandbox after testing. Do not leave
orphaned validation rules, flows, or approval processes.

---

## Token Reference

### NexusCRM token
```bash
curl -s -X POST "https://nexuscrm.pingcap.com/services/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=test-gosf-client&client_secret=test-gosf-secret-123"
```
Token format: JWT starting with `eyJ`

### Salesforce sandbox token
```bash
curl -s -X POST \
  https://pingcap--partialcop.sandbox.my.salesforce.com/services/oauth2/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=client_credentials' \
  --data-urlencode 'client_id=3MVG96vIeT8jJWjJmgzR59tsgAKUd1JtyGnX.Ljc7tLeqVcpNlm6mmdtnkn._7uOKTDwrUu6xrY7SQUUZXPfk' \
  --data-urlencode 'client_secret=622093F9567636D20B9078B3B960B6EA8FFAC97758326A414D7473508C872748'
```
Token format: Salesforce access token starting with `00D`

---

## Execution Protocol (Full Regression)

### Phase 1: API scenarios

```bash
cd tests/contract
CONTRACT_TARGET=local CONTRACT_BASE_URL=https://nexuscrm.pingcap.com \
  go test -v -count=1 ./scenarios/api/... -timeout 300s
```

### Phase 2: Diagnose API failures

```
Test fails
  +-- 500? --> Backend bug (check server logs)
  +-- 404? --> Route/data missing (unimplemented feature)
  +-- 400 wrong message? --> Validation logic bug
  +-- Test assertion wrong? --> Fix the test (with justification)
```

### Phase 3: Chrome scenarios on NexusCRM backend

Follow Mode 2 above for each `type: chrome` scenario.

### Phase 4: Chrome A/B comparison for reproduced bugs

For each bug reproduced in Phase 3, run the same scenario against the SF backend
(Mode 3) to confirm it is a NexusCRM-specific issue. This eliminates false
positives from frontend bugs or configuration differences.

### Phase 5: Report

```markdown
# Scenario Test Report -- [DATE]

## API Scenarios (X pass / X fail / X skip)
| ID | Name | Result | Notes |

## Chrome Scenarios (X reproduced / X pass / X excluded)
| ID | Name | SF Result | NexusCRM Result | Confirmed Bug? |

## Findings
### [ID]: [Name]
- Expected (SF behavior): ...
- Actual (NexusCRM behavior): ...
- Root cause: ...
- Evidence: screenshot / API response diff
```

---

## Troubleshooting

### "Session expired or invalid" on remote frontend
**Cause**: You are likely using the old HTTP deployment bookmark at `http://34.59.60.122:8079`.
`crypto.randomUUID()` requires a secure context (HTTPS or localhost).
**Fix**: Use `https://nexuscrm.pingcap.com` for direct NexusCRM validation, or
`localhost:5173` with a local Vite dev server for proxy-based A/B testing.

### "Failed to fetch" when connecting to Salesforce
**Cause**: CORS. The SF sandbox blocks cross-origin requests from `localhost`.
**Fix**: Use the Vite proxy method. Set `instance_url` to `http://localhost:5173`
(not the SF URL) so all requests route through Vite's proxy, which forwards to SF.

### "Internal Server Error" on login page
**Cause**: Vite proxy is forwarding to the wrong backend.
**Fix**: Check `frontend/.env` -- ensure `REACT_APP_BACKEND_URL` points to the
correct backend. Restart Vite after any `.env` change (`pkill -f vite && cd frontend && npm run dev`).

### "Object not found" when navigating to /object/Account
**Cause**: `instance_url` in localStorage contains `salesforce.com`, triggering the
frontend's "direct mode" which tries to call SF APIs directly (bypassing proxy).
**Fix**: Set `instance_url` to `http://localhost:5173` for SF backend testing. The
Vite proxy handles the actual routing.

### Flow Builder "Failed to load flow"
**Cause**: `crypto.randomUUID()` not available in non-secure context (HTTP).
**Fix**: Use localhost frontend, not the remote HTTP server.

### SF returns 503 for queries
**Cause**: Sandbox rate limiting.
**Fix**: Wait 30-60 seconds and retry. SF sandboxes have aggressive rate limits.

### Vite does not pick up .env changes
**Cause**: Vite caches environment at startup.
**Fix**: Kill Vite completely (`pkill -f vite`), then restart. A simple page
refresh is NOT sufficient.

### PATCH/POST returns 200 but data doesn't save (deployed environment)
**Cause**: Two possibilities:
1. CDN (CloudFront) returning HTML instead of forwarding to backend — curl sees this
   but browser may not (different headers)
2. A validation rule is silently blocking the update — backend returns 400 but frontend
   swallows the error without showing a toast
**Fix**: Use browser JS console `fetch()` to see the raw response status AND body:
```javascript
(async () => {
  const token = localStorage.getItem('auth_token');
  const resp = await fetch('/services/data/v63.0/sobjects/opportunity/ID', {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
    body: JSON.stringify({StageName: 'Technical Validation'})
  });
  const text = await resp.text();
  console.log('Status:', resp.status, 'Body:', text.substring(0, 300));
})()
```
**Real example**: Opportunity Stage edit returned `400` with `"Validation rule 'Replacing_DB_Remarks'
failed to evaluate (caused by: unknown name INCLUDES)"` — a different validation rule (not the
one being tested) blocked the update. This was invisible in the UI.

### Local TiDB vs TiDB Cloud — startup time difference
**Local TiDB**: `tiup playground` starts in seconds, backend ready in ~6s. But empty DB.
**TiDB Cloud**: Real data but 15-20 minute startup (schema reconciliation over network).
**Fix**: Use local TiDB for rapid iteration, TiDB Cloud for data-dependent tests.
Always re-verify on deploy after local testing.

### Chrome extension "No Chrome extension connected"
**Cause**: Multiple possible: Claude Desktop conflict, stale native host, extension disabled.
**Fix options** (in order):
1. Restart Chrome completely (Cmd+Q, reopen)
2. Toggle extension off/on in `chrome://extensions`
3. Visit pairing page: `chrome-extension://fcoeoabgfenejglbffodgkkbkcdhcgfn/pairing.html`
4. Kill stale processes: `pkill -f chrome-native-host`
5. Restart Claude Code with `claude --chrome`
6. Fallback: use `npx agent-browser` (doesn't need Chrome extension)

---

## Adding New Scenarios

1. Add entry to `scenarios.yaml` with id, name, description, type, test_file
2. For API: add test function to `api/*_test.go`
3. For Chrome: add `.md` to `chrome/` with these sections:
   - Status (REPRODUCED / NOT REPRODUCED / EXCLUDED)
   - Environment Setup
   - Steps (verified, with exact URLs and click targets)
   - Actual Result (NexusCRM)
   - Expected Result (from SF oracle or Salesforce documentation)
   - A/B Comparison (mandatory for confirmed bugs)
   - Root Cause (if known)
   - Failure Indicators (what to look for in browser)
   - Related (links to API tests)
4. Run the scenario on NexusCRM to verify reproduction
5. Run the scenario on SF to confirm it is a NexusCRM-specific issue

---

## Verified Chrome Scenarios (Quick Reference)

### FLOW-002: Flow Builder boolean display
```
URL: http://localhost:5173/setup/flows
Steps: Search "BoolTest" -> Scroll right -> Click edit (pencil) icon
SF: Assignment node visible with True/False value
NexusCRM: Assignment node missing from canvas entirely
Status: CONFIRMED BUG (backend)
```

### VAL-004: Validation rule UI error message
```
URL: http://localhost:5173/object/Account
Steps: Double-click record -> Edit -> Set Industry to "Technology" -> Save
SF: "Website must be blank..." (friendly validation message)
NexusCRM: "internal error: unknown name NOT (1:1)" (engine error leaked to UI)
Prerequisite: Validation rule with NOT(ISBLANK(Website)) on Account
Status: CONFIRMED BUG (backend)
```

### DATA-001: Field labels vs API names
```
URL: http://localhost:5173/object/Account
Steps: Observe column headers and record detail labels
SF: "Account Name" (human-readable Label)
NexusCRM: "Name" (ApiName variant)
Status: CONFIRMED BUG (backend)
```

### DATA-002: ListView column persistence
```
URL: http://localhost:5173/object/Account
Steps: Click Columns -> Select fields -> Apply -> Hard refresh (Cmd+Shift+R)
Check: localStorage.getItem('nexuscrm_columns_Account')
SF: Also uses localStorage (SELECTED 0)
NexusCRM: Also uses localStorage (SELECTED 0)
Status: NOT A BACKEND BUG -- frontend stores columns in localStorage on both backends
```

### DATA-003: Navigation label stability -- EXCLUDED
```
A/B comparison showed SF backend also has SYSTEM-only sidebar.
Not a backend issue -- App Navigation configuration.
Status: EXCLUDED
```

### APPR-002: Approval Queue record names
```
URL: http://localhost:5173/approvals
Steps: Page loads automatically
NexusCRM: All records show "Unknown", Object shows "Record"
SF: No pending approval data to compare against
Status: REPRODUCED on NexusCRM, no A/B baseline available
```

---

## Verification Hierarchy (MANDATORY)

**Every fix MUST be verified in the deployed environment via browser. No exceptions.**

```
Unit tests (least trust)    → proves logic in isolation, NOT that it works in production
Local backend + API tests   → proves backend logic, may miss real data/config issues
Local backend + Chrome      → proves UI flow, but local DB may lack real validation rules
curl against deploy         → UNRELIABLE — curl headers differ from browser (see lesson L1)
Deploy + Chrome (MOST trust)→ the ONLY authoritative verification
```

### Browser-in-deploy verification protocol

For EVERY fix, the final verification step MUST be:

1. **Open browser** to the deployed URL (e.g., `https://nexuscrm.pingcap.com`)
2. **Login as a real user** through the UI (not token injection)
3. **Perform the exact user action** that triggered the original bug
4. **Capture the actual response** — if the UI swallows errors silently, use browser JS console:
   ```javascript
   (async () => {
     const token = localStorage.getItem('auth_token');
     const resp = await fetch('/services/data/v63.0/sobjects/{object}/{id}', {
       method: 'PATCH',
       headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
       body: JSON.stringify({FieldName: 'NewValue'})
     });
     const text = await resp.text();
     console.log('Status:', resp.status, 'IsHTML:', text.includes('<html'), 'Body:', text.substring(0, 300));
   })()
   ```
5. **Never trust status code alone** — a 200 with HTML body is NOT success
6. **Never trust curl** as equivalent to browser behavior (different headers/cookies/CORS)

### Local backend deployment (for pre-deploy testing)

When the deployed environment is not yet updated, run the backend locally:

```bash
# Option 1: Local TiDB (fast — 6 second startup, but empty DB)
tiup playground --tag nexuscrm --db 1 --kv 1 --pd 1 --without-monitor --tiflash 0 &
mysql -h 127.0.0.1 -P 4000 -u root -e "CREATE DATABASE IF NOT EXISTS nexuscrm;"
export TIDB_HOST=127.0.0.1 TIDB_PORT=4000 TIDB_USER=root TIDB_PASSWORD="" TIDB_DATABASE=nexuscrm PORT=3001
cd backend && go run ./cmd/server

# Option 2: TiDB Cloud (has real data, but 15-20 min startup due to latency)
export TIDB_HOST="gateway01.us-west-2.prod.aws.tidbcloud.com" TIDB_PORT=4000
export TIDB_USER="38CEgTyThtdZFp3.root" TIDB_PASSWORD="k9H3eXkPczmtZdTm"
export TIDB_DATABASE=nexuscrm PORT=3001
cd backend && go run ./cmd/server
```

**Critical distinction**: Local TiDB is fast but has NO real data (no imported SF records,
no real validation rules like `Replacing_DB_Remarks` using `INCLUDES()`). Tests that pass
locally may fail on deploy because deployed DB has real SF-imported data with complex
formulas. **Always re-verify on the deployed environment after local testing.**

---

## Lessons Learned (from real mistakes — DO NOT repeat)

| # | Mistake | What happened | Correct approach |
|---|---------|--------------|-----------------|
| L1 | Used curl to test PATCH, got HTML, declared "CloudFront broken" | curl didn't send browser cookies/origin, CloudFront routed differently | Use browser JS `fetch()` or network tab — that's what the real user experiences |
| L2 | Declared "10/12 tests PASS" on local backend | Local DB had no real validation rules; deployed DB has `Replacing_DB_Remarks` using `INCLUDES()` which isn't implemented | Always verify on deployed environment with real imported data |
| L3 | Declared fix "verified" after unit tests pass | Unit tests create synthetic data; don't cover interaction with real SF-imported validation rules | Unit tests are necessary but NOT sufficient — deploy verification is mandatory |
| L4 | PATCH returned 200 in network tab, assumed success | 200 status but response body was HTML (CDN routing issue), or 200 but validation rule silently blocked the update | Always check response BODY, not just status code |
| L5 | Assumed API test results equal Chrome test results | API tests create their own data; Chrome tests use real imported data with complex formulas | Both layers needed — they catch different bugs |
| L6 | Test showed PASS but was actually tautological | Test asserted on data it created itself (e.g., created ProcessNode then queried its own data) | Red team audit every test: is the assertion value from the SERVER or from the TEST? |
| L7 | Used `t.Skip` when setup failed | Test suite showed green but nothing was actually tested | Use `t.Fatal` for setup failures — a skipped test is invisible in CI |
| L8 | Frontend silently swallowed PATCH 400 error | Changed Opportunity Stage, no error toast, but data didn't save | When UI action has no visible result, use JS console `fetch()` to see raw response |

---

## Rules

1. **Salesforce is always the oracle** -- if SF does it, NexusCRM must do it the same way
2. **Do not modify tests to make NexusCRM pass** -- fix the backend, not the test
3. **Every scenario maps to a real user-facing behavior** -- no synthetic edge cases
4. **Chrome scenarios must have verified, reproducible steps** -- tested by a human or agent
5. **A/B comparison is mandatory before attributing any issue to the backend** -- eliminates false positives
6. **Always use localhost frontend** -- never test against the remote HTTP frontend (crypto.randomUUID fails)
7. **Always route SF requests through Vite proxy** -- set instance_url to localhost, not salesforce.com
8. **Clean up SF sandbox test data** -- do not leave orphaned validation rules, flows, or approval processes
9. **Restart Vite after every .env change** -- the proxy target is cached at startup
10. **NEVER declare a fix verified without browser-in-deploy testing** -- local tests are necessary but NOT sufficient
11. **NEVER trust curl as equivalent to browser** -- use browser JS `fetch()` for the authoritative result
12. **NEVER assume local test environment matches deploy** -- deployed DB has real SF-imported data that local doesn't
13. **When UI action silently fails, use JS console `fetch()`** -- the frontend may swallow error responses
14. **Use `t.Fatal` not `t.Skip` for test setup failures** -- skipped tests are invisible failures
15. **Red team every test before trusting it** -- check if assertions test server behavior or just echo test's own data
10. **Document token format in evidence** -- NexusCRM JWT starts with `eyJ`, SF tokens start with `00D`
