Skip to content

Add CRM integrations with client prefill and saved screening results - #546

Open
prestoncabe wants to merge 10 commits into
mainfrom
543-crm-integrations
Open

prestoncabe wants to merge 10 commits into
mainfrom
543-crm-integrations

Conversation

@prestoncabe

@prestoncabe prestoncabe commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

CRMs can now open a published screener in an iframe or separate window, prefill answers from a client record, and receive evaluated results for an explicit save back to that record. Each integration owns its field mapping and persistence; BDT does not need CRM credentials.

  • Add a reusable host adapter and versioned browser message protocol with exact origin, window, session, and request checks. Screener builders configure allowed CRM origins on Publish; integrations are disabled until an origin is allowed and the screener is published again.
  • Preserve unanswered prefill values as unknown, evaluate initial answers, and discard stale responses. Report unavailable screeners, failed evaluations, connection timeouts, and disconnected host windows.
  • Add a standalone browser example that maps flat client fields into nested screener answers and displays saved results in the client record. Add a Salesforce example with nonprofit client intake, formatted results, permission metadata, and installation instructions.
  • Document the existing HTTP endpoints, browser protocol, mapping process, and integration lifecycle. Retain current main’s stale-result and evaluation-failure behavior.

Closes #543.

Verification

  • builder-frontend: npm test — 221 tests passed after integrating current main; the subsequent lifecycle regression test also passed in the focused 9-test Screener.test.tsx run.
  • builder-frontend: npm run build passed.
  • builder-api: mvn -Dtest=ScreenerResourceTest test — 7 tests passed, including CRM origin validation.
  • e2e: playwright test --project=crm-integrations — 7 Chromium tests passed. Covers iframe/popup workflows with object/JSON messages, prefill, edits, explicit persistence, disconnects, rejected origins, standalone behavior, and side-by-side results. API responses are intercepted; these tests do not modify Firebase.
  • Browser and Salesforce mapping checks — 4 tests passed each.
  • docs: npm run build passed; git diff --check passed.

Documentation impact

  • Added the developer CRM integration guide and navigation entry, plus browser and Salesforce example READMEs covering setup, mapping, persistence, and limitations.

@prestoncabe prestoncabe linked an issue Oct 10, 2026 that may be closed by this pull request
prestoncabe and others added 10 commits October 9, 2026 22:31
The demo server was a global Playwright webServer, so every project,
including the emulator-backed chromium suite, needed port 4174 free.
The CRM spec now launches it on a free port itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The host adapter required the path to be exactly /screener/{id}, so a
frontend deployed under a prefix such as /bdt was rejected as invalid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CRM bridge and array field normalization each kept their own list of
prototype keys. The host adapter keeps its own ID helper on purpose,
since CRMs copy that file as a standalone module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
form-js imports a null or omitted answer as its empty value, false for
checkboxes and '' for text fields. The connected screener then evaluated
and reported those as real answers, so a CRM could save an explicit No.
Those answers now stay null until the user changes the field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The screener sent ready once and waited indefinitely, so a reload after
the host disposed its connection, a host that started listening late,
or a redirect to another origin left staff on the waiting message.

The screener now repeats ready until initialized and, after 10 seconds,
shows the form unconnected with a notice. The host adapter ignores
repeated ready messages for an initialized session and reports
CONNECTION_TIMEOUT and ORIGIN_MISMATCH through onError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If fetching the published screener failed, the bridge had already sent
initialized and the host kept waiting for results that never came. The
screener now replies to initialize with a SCREENER_UNAVAILABLE error, or
sends it once loading fails, and shows a load error instead of throwing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the Salesforce component's record changed, or staff started another
screening, the host disposed its connection but the old popup kept
showing live results that nobody received. The host adapter's dispose()
now sends disconnect, and the screener stops sending results and tells
the user it's no longer connected. This covers any host, not only
Salesforce.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Any site could pass its own integrationOrigin, open or frame a published
screener, and receive every answer and result the person entered.

Builders now list allowed CRM origins in the Publish tab. They are saved
with the screener in normalized browser form, copied on publish, and
returned with the published screener. The screener starts its bridge
once that list has loaded. A host whose origin isn't listed gets an
ORIGIN_NOT_ALLOWED reply and nothing else, while the person still sees
a working standalone form with a notice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CRM integrations

1 participant