feat(forms): add Form A iframe fallback safeguards
This commit is contained in:
10 files changed
+299
-59
No files matched your search
@@ -10,8 +10,8 @@ The current Builder theme reconciliation adds narrowly scoped `!important` decla
|
||||
|
||||
## Installation and comparison
|
||||
|
||||
1. Create or open the approved blank Form A in AIA's HIPAA-enabled Jotform account.
|
||||
2. Keep the form disabled while field, PHI, notification, conditional-logic, and language review is pending.
|
||||
1. Open the approved Form A in AIA's HIPAA-enabled Jotform account. Do not change its fields or settings from this repository workflow.
|
||||
2. Keep production activation disabled while embedded-runtime, accessibility, telemetry/privacy, and release review remain pending.
|
||||
3. Paste the contents of `aia-form-a.css` into Jotform's custom CSS area.
|
||||
4. Compare the Jotform preview with the Astro Form A mock at 390px and 1440px. Check the form surface, label rhythm, input height, button fill, outlined hover/focus state, error state, and mobile reflow.
|
||||
5. Check both English and Spanish configurations. Confirm translated labels, required indicators, validation messages, confirmation copy, and the accessible iframe title supplied by the parent Astro integration.
|
||||
@@ -27,12 +27,15 @@ The Astro adapter exists only for a separately approved, non-indexing isolated t
|
||||
PUBLIC_AIA_FORMS_MODE=isolated-test
|
||||
PUBLIC_AIA_FORMS_ENVIRONMENT=isolated-test
|
||||
PUBLIC_AIA_FORMS_ISOLATED_TEST_APPROVED=true
|
||||
PUBLIC_AIA_FORMS_LIVE_APPROVED=false
|
||||
PUBLIC_ALLOW_INDEXING=false
|
||||
```
|
||||
|
||||
It uses fixed allowlisted hosted URLs only: the contact routes use `formContext=contact` with their fixed English or Spanish language value, and the five landing routes use `formContext=landing&language=en`. `formContext` is the exact case-sensitive Jotform Unique Name supplied by the owner; it is presentation-only and does not accept visitor query values. A missing gate, any other environment role, an indexing-enabled build, a route outside that seven-route allowlist, or `live` mode renders the normal fail-closed Astro fallback instead.
|
||||
|
||||
The adapter has a localized iframe title, a fixed-height responsive frame, a `no-referrer` policy, a minimal sandbox, and a direct hosted-form fallback. It loads no Jotform helper script, reads no iframe DOM, and treats no frame load as a submission or conversion. The disabled Form A must undergo owner-authorized synthetic EN/ES testing before the isolated adapter or its fixed height can be accepted for a live workflow.
|
||||
The adapter has a localized iframe title, a fixed-height responsive frame, a `no-referrer` policy, a minimal sandbox, and a direct hosted-form fallback. The fallback is outside the iframe, initially hidden without layout space, revealed after a 15-second missing-load timeout, cleared on frame load or component disposal, and duplicated only as localized `<noscript>` content. A frame `load` cancels the timeout but is never treated as Jotform initialization, accepted submission, conversion, or proof that the cross-origin application is healthy. It loads no Jotform helper script, reads no iframe DOM, and installs no `postMessage` handler.
|
||||
|
||||
Direct-Jotform Form A synthetic EN/ES submission, native thank-you, conditional-email/language, generic staff notification, autoresponder delivery, fixed Reply-To, no-answer/no-PDF and no-webhook/integration evidence is owner-confirmed. The owner also reports successful synthetic submission and confirmation through the isolated Astro iframe on English contact, Spanish contact and one ABA landing placement. This establishes owner-reported Form A functional submission acceptance for the sampled routes; it does not replace final seven-route responsive/keyboard/accessibility acceptance, Jotform runtime/network and telemetry privacy review, deployment review or site-release authorization.
|
||||
|
||||
### Landing-route extension — 2026-10-25 / 2026-10-26
|
||||
|
||||
@@ -57,15 +60,21 @@ The owner-approved Form A landing extension applies only to `/lp/aba-therapy`, `
|
||||
|
||||
## Verification checklist
|
||||
|
||||
- [ ] Form A is disabled while configuration is reviewed.
|
||||
- [ ] Exact question types, required flags, choices, conditional logic, and `formContext` values are approved.
|
||||
- [ ] English and Spanish labels, validation, confirmation, and button variants are reviewed by fluent-language owners.
|
||||
- [x] Direct English/Spanish Form A language handling, conditional email requirement, synthetic submission and native thank-you were owner-verified; this is not embedded-iframe acceptance.
|
||||
- [x] Direct staff-notification/autoresponder delivery, generic no-answer/no-PDF content, fixed Reply-To, recipient access/retention, and absent webhooks/integrations were owner-verified for Form A.
|
||||
- [ ] Exact remaining question properties, choices, conditional routing, availability and future Forms B/C configuration are approved.
|
||||
- [ ] English and Spanish embedded-iframe labels, validation, confirmation, and button variants are reviewed by fluent-language owners.
|
||||
- [ ] The hidden honeypot remains present, effective, and excluded from user-facing visual changes.
|
||||
- [ ] Exactly one native Submit button is visible for every valid context.
|
||||
- [ ] Invalid or missing context does not expose an unintended submission path.
|
||||
- [ ] No PHI-bearing answer, PDF, recipient, redirect, webhook, or HTTP POST forwarding is inherited without privacy/security approval.
|
||||
- [ ] The iframe title, loading/error state, keyboard behavior, and mobile height are accepted.
|
||||
- [ ] Synthetic testing uses only approved synthetic values and a separately authorized test form.
|
||||
- [x] Owner-reported embedded synthetic submission and confirmation passed on English contact, Spanish contact and one ABA landing placement; record the exact ABA path in the owner test log before release.
|
||||
- [ ] All seven placements have final responsive, keyboard, screen-reader and fluent-language acceptance, including timeout/fallback and error expansion.
|
||||
- [ ] Jotform runtime requests, telemetry and cross-origin failure behavior are reviewed with privacy/security owners.
|
||||
- [ ] No Jotform, Contabo, analytics, advertising, or production API request is made by off/mock builds.
|
||||
|
||||
JF-01 through JF-09, privacy/security review, operations approval, fluent-language review, and production activation remain pending. This guide does not authorize Jotform account changes or live integration.
|
||||
|
||||
## 2026-10-28 reconciliation
|
||||
|
||||
The current records supersede the earlier Form B mapping only for the five named landing journeys: all five now use Form A under the seven-route isolated-test gate. Form B remains for the other service-consultation journeys. The official Jotform JavaScript embed handler is **conditionally acceptable pending targeted verification**; it is not included in this adapter. Any future adoption must first verify its current source, origin and source-window validation, parent-page privileges, URL mutation, network requests, Firefox behavior and all supported error states. Fixed heights remain the current approved architecture until that review is separately accepted.
|
||||
@@ -1,6 +1,6 @@
|
||||
# AIA Astro Migration — Living Session Handoff
|
||||
|
||||
**Last updated:** October 20, 2026 (UTC)
|
||||
**Last updated:** October 28, 2026 (UTC)
|
||||
**Repository:** `AIAMarketing/azinstitute4autism.com`
|
||||
**Working branch:** `faithful-astro-migration`
|
||||
**Last accepted technical implementation:** Phase 9 bounded landing-page integration (presentation and inactive lead UI only; human decisions and production activation pending)
|
||||
@@ -152,8 +152,10 @@ trusted-source comparison. No insecure retrieval was repeated.
|
||||
header/footer, source section order, existing byte-identical local imagery and
|
||||
cache-qualified source evidence. It is self-canonical and adds one sitemap URL.
|
||||
- Omitted the sibling project's API, tracking, success-state and attribution
|
||||
runtime. All five visible lead panels remain inactive, with no native POST,
|
||||
live Jotform or conversion behavior. P9-01, P9-03, P9-05 and P9-07 remain open.
|
||||
runtime. Normal staging and indexing artifacts keep all five lead panels inactive,
|
||||
with no native POST, live Jotform or conversion behavior. The later seven-route
|
||||
Form A isolated-test adapter is separately gated and remains outside those artifacts.
|
||||
P9-01, P9-03, P9-05 and P9-07 remain open.
|
||||
- Added `/lp` as a temporary staging/testing index linking the five pages. It is
|
||||
always `noindex,nofollow`, sitemap/hreflang-excluded and omitted entirely when
|
||||
`PUBLIC_ALLOW_INDEXING=true`. This build gate is not access control.
|
||||
@@ -165,6 +167,12 @@ trusted-source comparison. No insecure retrieval was repeated.
|
||||
Earlier October 20 reconciliations remain superseded local evidence outside
|
||||
the checkpoint.
|
||||
|
||||
### Current Form A status — 2026-10-28 (uncommitted reconciliation; not a signed acceptance)
|
||||
|
||||
- Form A (`262946078314059`) is implemented only in a non-indexing, seven-route `isolated-test` adapter: `/contact`, `/es/contact`, the four campaign landing routes and `/lp/early-intervention`. The five landing journeys use fixed `formContext=landing&language=en`, superseding only their earlier Form B mapping. Form B remains for the other service-consultation journeys; Form C remains professional referrals.
|
||||
- Owner-confirmed direct-Jotform evidence covers synthetic EN/ES submission, native thank-you, conditional email/language, generic no-answer/no-PDF staff notification, autoresponder delivery, corrected fixed Reply-To, no webhooks/integrations, privacy scope, recipient access and retention. The owner also reports successful Astro-iframe synthetic submission and confirmation on English contact, Spanish contact and one ABA landing placement; record the exact third route before release. Functional submission acceptance is complete only for those owner-reported samples. All-seven-route accessibility, runtime-network/telemetry, deployment and site-release acceptance remain open.
|
||||
- The adapter retains fixed URLs, responsive fixed heights, `no-referrer`, `allow-forms allow-scripts allow-same-origin`, no helper script, and a hosted fallback that is hidden without space until a 15-second missing-load timeout. A frame load only clears that timeout; it does not prove cross-origin application health or accepted submission. The official Jotform resizing handler is conditionally acceptable pending separately authorized source, origin, privilege, Firefox and error-state verification; it is not implemented. Fixed heights remain current.
|
||||
|
||||
The indexing-enabled build has 127 HTML routes/canonicals and 109 sitemap URLs.
|
||||
Normal staging has 128 `noindex,nofollow` pages because it also contains `/lp`;
|
||||
its sitemap and hreflang are empty. Existing 180 hreflang links across 55 routes,
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Phase 9 — Form A owner acceptance record
|
||||
|
||||
**Recorded:** 2026-10-28 UTC
|
||||
**Scope:** Form A (`262946078314059`) only
|
||||
**Evidence type:** owner-reported Jotform and browser acceptance; not independently reproduced by this repository task.
|
||||
|
||||
## Accepted functional scope
|
||||
|
||||
The owner reports that Form A is approved for production use subject to the separate Astro production gate and site-release process. The owner reports successful direct-Jotform synthetic submissions in English and Spanish, native confirmation behavior, notification and autoresponder delivery, and deletion of the test submissions.
|
||||
|
||||
The owner also reports successful synthetic submission and confirmation through the isolated Astro iframe at:
|
||||
|
||||
- `/contact` (English contact)
|
||||
- `/es/contact` (Spanish contact)
|
||||
- one ABA landing placement
|
||||
|
||||
The owner report calls the third route an ABA landing page but does not name its exact path. This record deliberately does not infer a route from the four-route ABA landing allowlist. Record that path in the release test log before declaring all seven placements submission-tested.
|
||||
|
||||
These reported tests complete **Form A functional submission acceptance for the sampled direct and embedded journeys**. They do not independently prove that every allowed page placement has passed a full submission flow.
|
||||
|
||||
## Automated repository evidence
|
||||
|
||||
The repository's guarded test suite renders all seven allowlisted placements with fixed URLs and contexts:
|
||||
|
||||
| Route | Locale | Context | Submission evidence |
|
||||
|---|---|---|---|
|
||||
| `/contact` | EN | `contact` | Owner-reported embedded synthetic sample |
|
||||
| `/es/contact` | ES | `contact` | Owner-reported embedded synthetic sample |
|
||||
| `/lp/aba-therapy` | EN | `landing` | Automated rendering; may be the reported ABA sample only after route confirmation |
|
||||
| `/lp/aba-insurance` | EN | `landing` | Automated rendering only |
|
||||
| `/lp/aba-ahcccs` | EN | `landing` | Automated rendering only |
|
||||
| `/lp/aba-private-pay` | EN | `landing` | Automated rendering only |
|
||||
| `/lp/early-intervention` | EN | `landing` | Automated rendering only |
|
||||
|
||||
The automated checks do not submit data, inspect the cross-origin frame, infer conversion from `load`, or transmit Jotform, API, analytics or advertising requests.
|
||||
|
||||
## Embed-handler decision
|
||||
|
||||
The official Jotform resize/embed handler is **conditionally acceptable pending targeted verification**. It is not part of the current fixed-height adapter. Before any adoption, a separately authorized review must inspect its current source and dependencies, message-origin and source-window checks, URL mutation, parent-page privileges, network requests, sandbox interaction, Firefox behavior, validation/error expansion and failure states. No change to the fixed-height implementation is authorized by this decision record.
|
||||
|
||||
## Still required before a site release
|
||||
|
||||
- The exact submitted ABA landing route must be recorded.
|
||||
- All seven placements need final responsive, keyboard, screen-reader, error-state and fluent-language acceptance.
|
||||
- Privacy/security owners must review Jotform runtime requests and telemetry, embed/referrer behavior and deployment configuration.
|
||||
- Hosting owners must accept the HTTPS host, CSP/referrer/log policy and production environment gate.
|
||||
- Marketing notification routing remains a pre-cutover operational check; it is not changed by this record.
|
||||
- Forms B and C remain unimplemented and unverified.
|
||||
- Phase 10 requires a separate release-owner go/no-go and rollback rehearsal.
|
||||
|
||||
No Jotform account settings, recipient routing, submissions, analytics, advertising, API behavior, hosting configuration or deployment was changed while recording this evidence.
|
||||
@@ -1,10 +1,10 @@
|
||||
# P9-03 source-specific implementation plan
|
||||
|
||||
Date: **2026-10-18** · Plan version **1.3 revision 2026-10-27** · Governing contract: [P9-03-JF-2026-10-18 v1.3](phase-9-jotform-integration-contract-2026-10-18.md).
|
||||
Date: **2026-10-18** · Plan version **1.5 revision 2026-10-28** · Governing contract: [P9-03-JF-2026-10-18 v1.5](phase-9-jotform-integration-contract-2026-10-18.md).
|
||||
|
||||
**Planning only. Do not execute any batch from this document without separate authorization.** Phase 9 establishes decisions and bounded implementation authority. Phase 10 retains final production integration acceptance, rollback and cutover. The user stages, signs and pushes.
|
||||
|
||||
> **Form A progress note — 2026-10-25 (supplemental implementation evidence, no release approval):** The owner authorized a new blank-built **AIA | Website | General Enquiries** form (`262946078314059`) and reports successful browser checks of contact vs. compact visibility, conditional email requiredness, and the added Spanish language. Its hidden presentation field must have **Advanced → Field Details → Unique Name = `formContext`** (exact case); using only that text as its visible label did not enable URL prepopulation. See [Form A Builder/runtime verification](phase-9-form-a-jotform-runtime-verification-2026-10-25.md) for evidence, limitations, future Forms B/C key verification, and open gates. This records a separately authorized owner/connector activity since the original planning snapshot: it **does not** approve a live embed or submissions, modify the Form B mapping for the four campaign pages, certify notifications/PHI handling, or close JF/OA acceptance. Forms B and C remain planned; their field keys and IDs must not be guessed. The existing uncommitted Jotform integration README/CSS belong to a separate Codex workstream and are not overwritten here.
|
||||
> **Form A reconciliation — 2026-10-28 (current implementation evidence, no production release approval):** Form A — **AIA | Website | General Enquiries** (`262946078314059`) has owner-confirmed direct EN/ES synthetic submission, native thank-you, generic no-answer/no-PDF staff notification, autoresponder delivery, fixed hidden Reply-To, no-webhook/integration, conditional-email and language evidence. The owner also reports successful isolated Astro-iframe synthetic submission and confirmation on English contact, Spanish contact and one ABA landing placement; record that third route explicitly before release rather than inferring it. This completes functional submission acceptance for the reported samples, not seven-route responsive, accessibility, telemetry/privacy, deployment or site-release acceptance. Its hidden presentation field is **Advanced → Field Details → Unique Name = `formContext`** (exact case). The current Astro adapter is implemented only for gated, non-indexing isolated tests: `/contact`, `/es/contact`, `/lp/aba-therapy`, `/lp/aba-insurance`, `/lp/aba-ahcccs`, `/lp/aba-private-pay`, and `/lp/early-intervention`; the five landing journeys now use fixed `formContext=landing`, superseding their earlier Form B mapping. Forms B and C remain planned; their field keys and IDs must not be guessed.
|
||||
|
||||
## 1. Actual baseline and reconciliation
|
||||
|
||||
@@ -16,7 +16,7 @@ Current source-manifest construction confirmed nine eligible enquiry routes and
|
||||
|
||||
The owner reports that new **Form A — General Enquiries** (`262946078314059`) has field PHI protection; a generic notification without submitted values or PDF attachment; no webhooks or integrations; and privacy approval for that direct-to-Jotform configuration. This is owner-confirmed evidence from a ChatGPT WebUI/Jotform review, not a repository-side Builder audit. It does not authorize production activation, external submissions, analytics, or advertising.
|
||||
|
||||
The confirmation is limited. Recipient access and Form A retention/deletion controls are owner-verified, while exact role/retention-policy details remain account-policy information outside this repository. Exact conditional logic, default/reply-to/header behavior, redirects/confirmation, autosave/save-and-continue, availability/spam settings, full EN/ES runtime acceptance, and synthetic delivery acceptance remain pending. Forms B and C are not covered.
|
||||
The confirmation is limited. Recipient access and Form A retention/deletion controls are owner-verified, while exact role/retention-policy details remain account-policy information outside this repository. Direct conditional-email/language, Reply-To, notification/autoresponder delivery and native confirmation evidence is recorded, but the exact wider conditional logic, defaults, autosave/save-and-continue, availability/spam settings, embedded-iframe submission/confirmation, full responsive/accessibility acceptance and runtime network/privacy behavior remain pending. Forms B and C are not covered.
|
||||
|
||||
| Inventory ID | Route | Source content under `www/src/content/pages` | Owning component under `www/src/components` | Target |
|
||||
|---|---|---|---|---|
|
||||
@@ -32,7 +32,7 @@ The confirmation is limited. Recipient access and Form A retention/deletion cont
|
||||
|
||||
Nine instances = 7 EN + 2 ES, three implementations. Eleven GET search instances remain separate. No Arabic enquiry form or Spanish referral route is created. EN/ES-capable logical forms do not imply new published routes.
|
||||
|
||||
The five imported landing-page journeys—`/lp/aba-therapy`, `/lp/aba-insurance`, `/lp/aba-ahcccs`, `/lp/aba-private-pay` and `/lp/early-intervention`—are accounted for as future Form B destinations. Their inactive consultation panels are not additional native form implementations and do not have Jotform IDs.
|
||||
The five imported landing-page journeys—`/lp/aba-therapy`, `/lp/aba-insurance`, `/lp/aba-ahcccs`, `/lp/aba-private-pay` and `/lp/early-intervention`—are current gated Form A isolated-test placements with fixed `formContext=landing&language=en`. This supersedes their earlier Form B mapping only for these journeys. Form B remains the future destination for other service-consultation routes that need its additional approved fields. The landing panels are not additional native form implementations, and Form A is absent from off/mock/indexing output.
|
||||
|
||||
### Discrepancies and supersession notes
|
||||
|
||||
@@ -97,7 +97,7 @@ For off mode prefer non-form fallback markup without editable lead controls or a
|
||||
|
||||
Mock fixtures must be synthetic and inert to external requests, including Enter and native submission paths; Playwright aborts unapproved requests before transmission. Fixture URLs/assets must not appear in the production route manifest. Live descriptors must not leak into off/mock emitted bundles through shared imports—scan generated HTML, scripts and preload hints.
|
||||
|
||||
Parent component preserves approved page design, explanatory text, iframe title, hosted fallback and responsive width/height. Cross-origin child labels/errors must be verified in Jotform; Astro locale and CSS do not localize/fix them. No guessed `?language=es`, hidden-field mapping, completion event or helper script. Loading/error UI must not claim it can inspect the child DOM. A frame load is not proof of availability or accepted submission.
|
||||
Parent component preserves approved page design, explanatory text, iframe title, hosted fallback and responsive width/height. The Form A adapter uses owner-verified fixed `language=en|es` and `formContext=contact|landing` values only; it forwards no visitor data. Cross-origin child labels/errors must be verified in Jotform; Astro locale and CSS do not localize/fix them. The first-party fallback remains hidden without layout space, appears only after a 15-second missing-load timeout, and has a localized noscript version. Loading/error UI must not claim it can inspect the child DOM. A frame load is not proof of availability, application initialization or accepted submission.
|
||||
|
||||
Drawer decision is a prerequisite for replacing its live content: retain timer only if approved and incapable of loading vendor resources without the approved trigger; otherwise use an explicitly approved open/load interaction or full-page fallback. Preserve Escape/initial focus/restoration and test cross-origin focus boundaries. No wholesale dialog replacement.
|
||||
|
||||
@@ -145,7 +145,7 @@ The owner separately reports that the existing `AIA Lead Generator | plain` form
|
||||
- **Objective:** verify JF-09 and runtime JF-03/08 with actual approved isolated form(s).
|
||||
- **Authorization:** separately approve test form IDs, origins, environment, synthetic input set, notification recipient roles, permitted methods/network destinations and test window. This is not covered by GET-only prior browser authorization.
|
||||
- **Prerequisites:** Three blank forms created and their configuration review recorded in B; C mode guards; operations/privacy/language settings approved. Use loopback preview, exact test-only allowlist; production counters/maps/Google/unrelated resources blocked.
|
||||
- **Acceptance:** actual EN/ES submit/error/cancel/retry behavior; delivered generic emails and headers inspected in a controlled workflow; no answers/identifiers/attachments; no Contabo forwarding; iframe/fallback, keyboard, real screen reader where available, 200%/400% zoom, slow/offline/block scenarios. Use no real patient data; retain only sanitized results.
|
||||
- **Acceptance:** for Forms B/C, actual approved EN/ES submit/error/cancel/retry behavior and controlled generic-email/header inspection. For Form A, owner-reported direct-Jotform and sampled Astro-iframe synthetic submission/confirmation are complete; final fallback, keyboard, real screen reader where available, 200%/400% zoom, slow/offline/block scenarios, all-seven-route acceptance and runtime-network review remain pending. Use no real patient data; retain only sanitized results.
|
||||
- **Stop:** test-form evidence and remaining gates; no production IDs or deployment.
|
||||
|
||||
### Batch E — Approved production mapping and staged-site integration
|
||||
@@ -236,3 +236,7 @@ Roster reconciliation, version 1.1: only the October 18 contract, plan and check
|
||||
Decision revision, version 1.2, 2026-10-21: the owner superseded the working-copy proposal with three new blank Jotform forms. Batch A now finalizes specifications, Batch B creates the three disabled forms only after explicit mutation authorization, and later batches retain off/mock/live safeguards. The 27-form legacy roster and all existing workflows remain historical/operationally independent. No source components, tests, Jotform forms or production systems were changed.
|
||||
|
||||
Version 1.3, 2026-10-27: records owner-confirmed Form A configuration evidence from the owner’s ChatGPT WebUI/Jotform review. Form A field PHI protection, the generic no-PHI notification template, absence of submission-PDF attachment, absence of webhooks/integrations, and privacy approval are confirmed for that form’s configuration scope. The update records recipient access and retention/deletion as owner-verified for Form A. It does not close conditional routing, runtime acceptance, live activation, Forms B/C configuration or any broader P9-03/P9-07 gate.
|
||||
|
||||
Version 1.4, 2026-10-28: reconciles owner-confirmed direct Form A runtime evidence and the implemented seven-route isolated iframe adapter. The five landing routes now use Form A under the isolated-test gate, superseding their earlier Form B mapping; Form B remains for other service-consultation journeys. Direct tests do not close embedded-iframe, runtime/privacy, accessibility or production gates.
|
||||
|
||||
Version 1.5, 2026-10-28: records owner-reported embedded synthetic submission and confirmation on the English and Spanish contact routes plus one ABA landing placement. It completes Form A functional submission acceptance for the reported samples, while retaining route-specific confirmation, all-seven-route responsive/accessibility, runtime/privacy, deployment and release gates.
|
||||
@@ -1,10 +1,10 @@
|
||||
# P9-03 Jotform integration contract
|
||||
|
||||
**Contract ID:** P9-03-JF-2026-10-18 · **Version:** 1.3 · **Decision revision:** 2026-10-27
|
||||
**Contract ID:** P9-03-JF-2026-10-18 · **Version:** 1.5 · **Decision revision:** 2026-10-28
|
||||
|
||||
**Status: documented planning architecture; implementation and activation NOT authorized.**
|
||||
|
||||
This is the authoritative P9-03 planning contract for the owner decisions supplied on October 18 and revised through October 27. It supersedes conflicting *proposals* in the [October 17 inventory](phase-9-form-entry-point-inventory-2026-10-17.md), without rewriting that historical record. It does not supersede project policy, approve clinical/legal content beyond the Form A configuration scope recorded below, close the Phase 9 register, or authorize production changes. Future changes require an explicit owner decision, a version/change record and evidence links. Version 1.0 records the original planning instruction; version 1.1 adds the owner-supplied complete roster; version 1.2 supersedes the copy-based Form B proposal with three new blank forms; version 1.3 records limited owner-confirmed Form A configuration evidence. No future commit SHA is invented.
|
||||
This is the authoritative P9-03 planning contract for the owner decisions supplied on October 18 and revised through October 28. It supersedes conflicting *proposals* in the [October 17 inventory](phase-9-form-entry-point-inventory-2026-10-17.md), without rewriting that historical record. It does not supersede project policy, approve clinical/legal content beyond the Form A configuration scope recorded below, close the Phase 9 register, or authorize production changes. Future changes require an explicit owner decision, a version/change record and evidence links. Version 1.0 records the original planning instruction; version 1.1 adds the owner-supplied complete roster; version 1.2 supersedes the copy-based Form B proposal with three new blank forms; version 1.3 records limited owner-confirmed Form A configuration evidence; version 1.4 reconciles completed direct-Jotform testing and the seven-route isolated iframe adapter; version 1.5 records owner-reported embedded synthetic acceptance for sampled routes without treating it as seven-route release acceptance. No future commit SHA is invented.
|
||||
|
||||
Companions: [source-specific implementation plan](phase-9-jotform-implementation-plan-2026-10-18.md), [verification and approval ledger](phase-9-jotform-verification-checklist-2026-10-18.csv).
|
||||
|
||||
@@ -50,7 +50,24 @@ The owner supplied the following **Form A-specific configuration evidence** afte
|
||||
- Form A has no webhooks or third-party integrations configured.
|
||||
- The owner reports privacy approval for these Form A controls and the direct-to-Jotform boundary.
|
||||
|
||||
This confirmation includes owner-verified notification-recipient access and Form A submission-retention controls. The exact authorized roles and retention period remain account-policy information and are intentionally not copied into the repository. Conditional routing, reply-to/default-header behavior, autoresponder policy, redirect/thank-you behavior, EN/ES runtime behavior, spam/availability settings, and successful synthetic submission remain pending. It does not enable Form A, approve an iframe in a production artifact, or close P9-03, P9-05, P9-07, or Phase 10 release approval. Forms B and C remain separately planned and unverified.
|
||||
This confirmation includes owner-verified notification-recipient access and Form A submission-retention controls. The exact authorized roles and retention period remain account-policy information and are intentionally not copied into the repository. The later owner-confirmed direct-Jotform runtime evidence is recorded in section 2.2. It does not enable Form A, approve an iframe in a production artifact, or close P9-03, P9-05, P9-07, or Phase 10 release approval. Forms B and C remain separately planned and unverified.
|
||||
|
||||
### 2.2 Form A direct runtime evidence and isolated iframe status — 2026-10-28
|
||||
|
||||
The owner reports the following **direct-Jotform synthetic test evidence** for Form A. These tests were performed through Jotform itself; the repository did not submit data, inspect submissions, or access the Jotform account.
|
||||
|
||||
- English and Spanish synthetic submissions completed successfully.
|
||||
- Native Jotform thank-you behavior was verified.
|
||||
- Staff-notification and family-autoresponder delivery were verified. The generic staff notification contained no submitted answers or PDF attachment.
|
||||
- Staff Reply-To was corrected with a fixed hidden email field and verified in Thunderbird.
|
||||
- No webhooks or third-party integrations are configured.
|
||||
- The conditional email requirement and language handling were verified.
|
||||
|
||||
The owner additionally reports successful synthetic submission and native confirmation through the isolated Astro iframe on `/contact`, `/es/contact`, and one ABA landing placement. The owner report identifies the third placement only as an ABA landing page; record its exact route in the release test log rather than inferring it from the allowlist. These tests establish Form A's **functional submission acceptance** for the reported direct and embedded samples. The repository did not reproduce the submissions or inspect their answers.
|
||||
|
||||
The remaining four or five landing placements, depending on the exact sampled ABA route, have automated rendering and fixed-URL coverage only. The current adapter is restricted to the seven placements listed in section 5 and to a separately gated, non-indexing `isolated-test` build. Its frame `load` event is only a transport signal; it is never treated as Jotform initialization, an accepted submission, or a conversion.
|
||||
|
||||
Open Form A gates are therefore: final responsive, keyboard, screen-reader and fluent-language acceptance across all seven placements; Jotform runtime/network and telemetry privacy review; deployment/hosting acceptance; and final site-release authorization.
|
||||
|
||||
## 3. Account assets and accounting for all 27
|
||||
|
||||
@@ -178,7 +195,7 @@ Cross-checked against F-001–F-009 in the local CSV, current source and eligibl
|
||||
|
||||
Three implementations, nine instances: seven EN, two ES, zero AR. Eleven GET search instances are not lead forms and must not be disabled or routed to Jotform. No new Arabic form or translated publication is authorized. Spanish service pages currently use Spanish consultation CTAs, not drawers. Arabic visitors retain established English fallbacks and clear language notices. All three logical forms prefer EN/ES capability, but no Spanish referral page or new route is implied.
|
||||
|
||||
The five imported landing-page journeys—`/lp/aba-therapy`, `/lp/aba-insurance`, `/lp/aba-ahcccs`, `/lp/aba-private-pay` and `/lp/early-intervention`—are all future Form B destinations. Their current consultation panels remain inactive; they do not add five native form implementations or new Jotform IDs.
|
||||
The five imported landing-page journeys—`/lp/aba-therapy`, `/lp/aba-insurance`, `/lp/aba-ahcccs`, `/lp/aba-private-pay` and `/lp/early-intervention`—are owner-approved **Form A** isolated-test placements, each using fixed `formContext=landing&language=en`. This supersedes their earlier Form B mapping only for these five journeys. Form B remains for other service-consultation journeys requiring its additional approved fields. The five panels do not add five native form implementations, and Form A remains unavailable in normal, indexing-enabled and production-shaped artifacts.
|
||||
|
||||
Tour's actual CTA currently goes to `/client-consultation` (B destination). A is the provisional proposed Tour mapping, **not a completed route change**. Fundraising awaits owner confirmation. Historical event requests remain separate; do not reinterpret the historical Eventbrite link as the account's Event Submission Form without evidence. `/client-forms` retains existing clinical, agreement, Sign and CentralReach links. The six repository-linked Jotform destinations are not automatically matched to the four named account assets.
|
||||
|
||||
@@ -200,7 +217,7 @@ The connector supplied titles, IDs, enabled status, timestamps, labels and limit
|
||||
| JF-08 | Approved HTTPS origin, hosted/iframe URLs, dimensions/mobile, scripts, referrer, locale and fallback | Account/hosting/accessibility owners; exported code and later authorized runtime evidence |
|
||||
| JF-09 | Actual EN/ES synthetic submission, notifications, success/failure, redirects and privacy boundary | **Later separately authorized** isolated form/environment; never real patient data |
|
||||
|
||||
For Form A only, JF-01 (field PHI protection), JF-04 (generic no-PHI notification, recipient access and no PDF attachment), JF-06 (no webhooks/integrations), and the corresponding privacy/access/retention portions of JF-07/OA-05 are owner-verified as of 2026-10-27. Each is **partial**: the remaining verification listed in its row is still required. JF-02, JF-03, JF-05, JF-08 and JF-09 remain pending. Read-only Builder review may complete other configuration portions; runtime portions remain pending until explicitly authorized. Generic vendor documentation does not close a gate. The [CSV ledger](phase-9-jotform-verification-checklist-2026-10-18.csv) tracks the exact scope, remaining work and supplied complete roster.
|
||||
For Form A only, JF-01 (field PHI protection), JF-04 (generic no-PHI notification, corrected fixed Reply-To, tested notification/autoresponder delivery, recipient access and no PDF attachment), JF-06 (no webhooks/integrations), and the corresponding privacy/access/retention portions of JF-07/OA-05 are owner-verified. JF-03 is partially verified for direct English/Spanish language handling and the conditional email requirement. JF-05 is partially verified for direct native thank-you behavior. JF-09 functional submission acceptance is owner-reported as complete for direct EN/ES and embedded English contact, Spanish contact and one ABA landing sample. The exact third embedded route remains to be recorded; that prevents treating the evidence as seven-route acceptance. JF-02 and the remaining portions of JF-03, JF-05, JF-07, JF-08 and JF-09—including seven-route accessibility, runtime/network privacy and release scope—remain pending. Read-only Builder review may complete other configuration portions; generic vendor documentation does not close a gate. The [CSV ledger](phase-9-jotform-verification-checklist-2026-10-18.csv) tracks the exact scope, remaining work and supplied complete roster.
|
||||
|
||||
## 7. Future integration-mode contract
|
||||
|
||||
@@ -210,13 +227,14 @@ Suggested semantic setting: `PUBLIC_AIA_FORMS_MODE`, exact values `off`, `mock`,
|
||||
|---|---|
|
||||
| off | No active lead form/empty POST action, live iframe, Jotform preconnect/helper/network request, or analytics. Render approved localized contact fallback; no native-submit bypass on inactive lead UI |
|
||||
| mock | Synthetic local-only fixture, no external endpoint or real data; safe guarded Playwright use; no Jotform ID required |
|
||||
| live | Separate owner/deployment approval; exact approved IDs/HTTPS origins/URLs and build/deploy gate; no automatic original-form fallback. Distinguish authorized isolated test form from authorized production form |
|
||||
| isolated-test | Form A only: requires `PUBLIC_AIA_FORMS_MODE=isolated-test`, `PUBLIC_AIA_FORMS_ENVIRONMENT=isolated-test`, `PUBLIC_AIA_FORMS_ISOLATED_TEST_APPROVED=true`, `PUBLIC_AIA_FORMS_LIVE_APPROVED=false`, `PUBLIC_ALLOW_INDEXING=false`, and a fixed seven-route allowlist. It is not a production mode. |
|
||||
| live | Separate owner/deployment approval; exact approved IDs/HTTPS origins/URLs and build/deploy gate; no automatic original-form fallback. The current Form A adapter rejects it. |
|
||||
|
||||
Off/mock output must not contain **new lead embed IDs/URLs or the historical `250727122848156` reference ID** in HTML, scripts, preload hints or client bundles. Existing clinical/Sign links intentionally remain in the site; they already contain production destinations. Preserving them conflicts with a blanket “no production ID anywhere” assertion, so this contract's new-embed restriction is scoped to the new lead integration. Existing user-initiated clinical navigation is not permission for automatic loading or test submissions. Do not claim the whole site has zero vendor destinations.
|
||||
|
||||
A separately approved isolated test can exercise live integration capability with **test-only mappings** and controlled notifications. Normal staging defaults off and cannot inherit production mappings. `PUBLIC_ALLOW_INDEXING` is independent and provides no privacy isolation. Forms modes do not change the separate likes/views API policy or existing map resource behavior; guarded tests still block those endpoints.
|
||||
|
||||
Reusable iframe requirements: exact approved HTTPS origin and URL, localized accessible title, responsive width and tested height, visible hosted fallback, loading/error explanation, mobile scroll, keyboard/focus support and minimal permissions. No arbitrary queries, unreviewed helper scripts, guessed language keys or untrusted cross-origin events. Frame `load` is not success. CSP/referrer policy require review; a parent policy does not control all resources inside the cross-origin document. Do not introduce a Contabo CORS proxy.
|
||||
Reusable iframe requirements: exact approved HTTPS origin and URL, localized accessible title, responsive width and tested height, keyboard/focus support and minimal permissions. The Form A adapter keeps a direct hosted-form fallback hidden without layout space, reveals it after a 15-second missing-load timeout, cancels that timeout on `load`/disposal, and exposes a localized `<noscript>` fallback. A frame `load` only ends the timeout; it is not proof of Jotform initialization, application health, accepted submission or conversion, and cannot diagnose cross-origin application errors. No arbitrary queries, unreviewed helper scripts, guessed language keys or untrusted cross-origin events. CSP/referrer policy require review; a parent policy does not control all resources inside the cross-origin document. Do not introduce a Contabo CORS proxy.
|
||||
|
||||
The existing ten-second drawer timer must not cause Jotform loading without explicit UX/privacy approval. Retention of auto-opening, explicit open and click-to-load are pending options. No wholesale dialog redesign is authorized. If embedding cannot satisfy usability/accessibility/privacy, use the approved hosted fallback.
|
||||
|
||||
@@ -281,3 +299,7 @@ Version 1.1, 2026-10-18: adds the complete owner-supplied 27-ID roster and disti
|
||||
Version 1.2, 2026-10-21: records the owner's decision to create three new blank Jotform forms from scratch. Supersedes the earlier working-copy proposal for `250727122848156`; preserves that ID as historical reference evidence only. Adds the owner-reported legacy-form remediation summary, retains all existing workflows, changes Batch B and OA-03 to blank-form creation, and leaves JF-01–09 and all owner approvals Pending/Unverified. No Jotform access or mutation occurred during this documentation revision.
|
||||
|
||||
Version 1.3, 2026-10-27: records owner-confirmed Form A (`262946078314059`) evidence from the owner’s ChatGPT WebUI/Jotform review: field PHI protection, the generic no-PHI notification template, no submission-PDF attachment, no webhooks/integrations, and privacy approval for that configuration scope. It marks only the corresponding Form A portions of JF-01, JF-04, JF-06, JF-07 and OA-05 in the ledger. Recipient access and retention are owner-verified; routing/defaults, runtime acceptance and activation remain open; no account access, mutation, submission, deployment or tracking occurred in this repository update.
|
||||
|
||||
Version 1.4, 2026-10-28: records owner-confirmed direct Form A EN/ES synthetic submission, native thank-you, notification/autoresponder delivery, fixed hidden Reply-To, no-answer/no-PDF staff notification, no webhook/integration, conditional-email and language evidence. It reconciles the five landing journeys to gated Form A isolated-test placements, documents the conditional hosted fallback, and preserves the remaining embedded-iframe, runtime/privacy and production gates. No repository-side Jotform access, submission, activation or deployment occurred.
|
||||
|
||||
Version 1.5, 2026-10-28: records the owner's successful embedded synthetic submission and native-confirmation report for English contact, Spanish contact and an ABA landing placement. It marks Form A functional submission acceptance complete only for those owner-reported samples, explicitly keeps the third route name to be recorded, and retains seven-route accessibility, runtime/privacy, deployment and site-release gates. It changes the official Jotform embed-handler decision from rejected to conditionally acceptable pending a separately authorized source, origin, privilege and Firefox/error-state review; the handler is not implemented.
|
||||
@@ -1,13 +1,13 @@
|
||||
gate_id,subject,verification_required,owner_role,evidence_source,status,approval_date,authorization_required
|
||||
JF-01,Question properties,"Types, required flags, defaults, exact options, visibility, internal identifiers, field-level PHI","Account owner + operations/privacy; Builder properties, no answers",Owner-confirmed 2026-10-27 ChatGPT WebUI/Jotform review: Form A (262946078314059) submitted fields are PHI-protected; all other properties remain unverified,Partially verified — Form A field PHI protection only,2026-10-27,Separate batch authorization; no action authorized by this checklist
|
||||
JF-02,Conditions,"Show/hide, skip, calculations, update-field logic, conditional recipients and submission routing",Account owner; sanitized Conditions settings,Sanitized Builder configuration and owner approval; authorized runtime evidence where required,Pending/Unverified,,Separate batch authorization; no action authorized by this checklist
|
||||
JF-03,Languages,"EN/ES support, complete translation, default/selector, errors, conditional logic, confirmation and embed locale behavior",Account owner + fluent reviewers; Builder plus later authorized runtime tests; never assume `?language=es`,Sanitized Builder configuration and owner approval; authorized runtime evidence where required,Pending/Unverified,,Separate batch authorization; no action authorized by this checklist
|
||||
JF-04,Emails,"Notifications/autoresponders, recipient roles, subject/body/dynamic tags, attachments, reply-to, conditional routes, default IDs/links, inherited production recipients or defaults","Account/privacy owner; sanitized settings, no recipient addresses in public reports","Owner-verified 2026-10-27 ChatGPT WebUI/Jotform review: Form A uses the generic no-PHI notification template, has no submission-PDF attachment, and has approved recipient access; routing, headers/defaults, reply-to, autoresponder and delivery acceptance remain unverified","Partially verified — Form A generic template, recipient access and no PDF attachment",2026-10-27,Separate batch authorization; no action authorized by this checklist
|
||||
JF-05,Thank-you behavior,"Native confirmation, redirects, answer echo, all targets, HTTP POST forwarding",Account/privacy owner; Builder; submission-data POST to Astro/Contabo OFF,Sanitized Builder configuration and owner approval; authorized runtime evidence where required,Pending/Unverified,,Separate batch authorization; no action authorized by this checklist
|
||||
JF-03,Languages,"EN/ES support, complete translation, default/selector, errors, conditional logic, confirmation and embed locale behavior",Account owner + fluent reviewers; Builder plus later authorized runtime tests; never assume `?language=es`,"Owner-confirmed 2026-10-28 direct-Jotform tests: English and Spanish rendering, language handling, and conditional email requirement verified; embedded-iframe language/validation/confirmation remains open",Partially verified — Form A direct language and conditional-email behavior,2026-10-28,Separate batch authorization; no action authorized by this checklist
|
||||
JF-04,Emails,"Notifications/autoresponders, recipient roles, subject/body/dynamic tags, attachments, reply-to, conditional routes, default IDs/links, inherited production recipients or defaults","Account/privacy owner; sanitized settings, no recipient addresses in public reports","Owner-confirmed 2026-10-28 direct-Jotform tests: generic staff notification contained no submitted answers or PDF attachment; staff notification and family autoresponder delivered; fixed hidden Reply-To verified in Thunderbird. Recipient roles, routing/defaults and embedded-runtime acceptance remain open","Partially verified — Form A generic messages, delivery and fixed Reply-To",2026-10-28,Separate batch authorization; no action authorized by this checklist
|
||||
JF-05,Thank-you behavior,"Native confirmation, redirects, answer echo, all targets, HTTP POST forwarding",Account/privacy owner; Builder; submission-data POST to Astro/Contabo OFF,"Owner-confirmed 2026-10-28 direct-Jotform tests: native thank-you behavior verified. Redirect, answer echo, POST-forwarding settings and Astro-embedded confirmation remain open",Partially verified — Form A direct native thank-you,2026-10-28,Separate batch authorization; no action authorized by this checklist
|
||||
JF-06,Integrations,"Integrations, webhooks, exports, destinations, Google/analytics, protected-data flows","Account/security/privacy owner; actual integration inventory, not cloning assumptions",Owner-confirmed 2026-10-27 ChatGPT WebUI/Jotform review: Form A has no webhooks or integrations; export/default/vendor behaviors beyond that reported review remain unverified,Verified — Form A webhooks/integrations absent,2026-10-27,Separate batch authorization; no action authorized by this checklist
|
||||
JF-07,Security and availability,"HIPAA/form/field protection, spam, availability, prefill, save/continue, access, retention, limits and duplicates",Account/privacy/security owner; settings and approved operational policy,"Owner-verified 2026-10-27: privacy approval covers Form A PHI protection, generic no-PHI notification, no PDF attachment, no webhooks/integrations, recipient access and retention/deletion; other security/availability controls remain unverified","Partially verified — Form A approved configuration, access and retention scope",2026-10-27,Separate batch authorization; no action authorized by this checklist
|
||||
JF-08,Embedding,"Approved HTTPS origin, hosted/iframe URLs, dimensions/mobile, scripts, referrer, locale and fallback",Account/hosting/accessibility owners; exported code and later authorized runtime evidence,Sanitized Builder configuration and owner approval; authorized runtime evidence where required,Pending/Unverified,,Separate batch authorization; no action authorized by this checklist
|
||||
JF-09,Controlled synthetic acceptance,"Actual EN/ES synthetic submission, notifications, success/failure, redirects and privacy boundary",Later separately authorized isolated form/environment; never real patient data,Sanitized Builder configuration and owner approval; authorized runtime evidence where required,Pending/Unverified,,Separate batch authorization; no action authorized by this checklist
|
||||
JF-08,Embedding,"Approved HTTPS origin, hosted/iframe URLs, dimensions/mobile, scripts, referrer, locale and fallback",Account/hosting/accessibility owners; exported code and later authorized runtime evidence,"Repository evidence 2026-10-28: fixed Form A origin/URLs, no-referrer policy, minimal sandbox, localized conditional hosted fallback and seven-route isolated-test gate. Owner reports embedded synthetic submission/confirmation on EN contact, ES contact and one ABA landing placement; exact third route and all-seven-route keyboard/responsive/runtime-network acceptance remain open",Partially verified — owner-reported sampled embedded acceptance plus adapter/fallback,2026-10-28,Separate batch authorization; no action authorized by this checklist
|
||||
JF-09,Controlled synthetic acceptance,"Actual EN/ES synthetic submission, notifications, success/failure, redirects and privacy boundary",Later separately authorized isolated form/environment; never real patient data,"Owner-confirmed 2026-10-28 direct-Jotform EN/ES and sampled Astro-iframe synthetic submissions with native thank-you and notification/autoresponder delivery. Functional submission acceptance is complete for the reported samples; exact ABA landing route, all-seven-route runtime/privacy and production-release acceptance remain open",Owner-verified — Form A functional submission samples,2026-10-28,Separate batch authorization; no action authorized by this checklist
|
||||
OA-01,Intake/operations,"Minimum fields, required flags/contact preference consistency, follow-up/callback expectations, Tour/fundraising scope",Intake/operations,Dated owner approval with scope and sanitized supporting evidence,Pending/Unverified,,Separate batch authorization; no action authorized by this checklist
|
||||
OA-02,Clinical leadership,"Eligibility, service/evaluation availability, private-pay Social Club distinction, referral boundary and approved claims",Clinical leadership,Dated owner approval with scope and sanitized supporting evidence,Pending/Unverified,,Separate batch authorization; no action authorized by this checklist
|
||||
OA-03,Jotform account owner,"JF-01–08, blank-form creation, actual new IDs/configuration and preservation of all existing forms",Jotform account owner,Dated owner approval with scope and sanitized supporting evidence,Pending/Unverified,,Separate batch authorization; no action authorized by this checklist
|
||||
|
||||
|
@@ -13,18 +13,6 @@ const { config, heading, description, variant = 'standalone' } = Astro.props;
|
||||
if (!config.enabled || !config.url || !config.title || !config.fallbackText || !config.fallbackLinkText) {
|
||||
throw new Error('JotformIframe requires an enabled, fully validated Form A iframe configuration.');
|
||||
}
|
||||
|
||||
const escapeHtml = (value: string) => value.replace(/[&<>"']/g, (character) => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
})[character] ?? character);
|
||||
|
||||
// Astro treats iframe descendants as raw text. set:html emits standards-based
|
||||
// iframe fallback markup while preserving the fixed, server-validated URL.
|
||||
const fallbackMarkup = `<p class="jotform-iframe__fallback">${escapeHtml(config.fallbackText)} <a href="${escapeHtml(config.url)}" target="_blank" rel="noopener noreferrer">${escapeHtml(config.fallbackLinkText)}</a></p>`;
|
||||
---
|
||||
|
||||
<section
|
||||
@@ -47,10 +35,67 @@ const fallbackMarkup = `<p class="jotform-iframe__fallback">${escapeHtml(config.
|
||||
loading="eager"
|
||||
referrerpolicy="no-referrer"
|
||||
sandbox="allow-forms allow-scripts allow-same-origin"
|
||||
set:html={fallbackMarkup}
|
||||
data-jotform-iframe-frame
|
||||
></iframe>
|
||||
<p class="jotform-iframe__fallback" data-jotform-iframe-fallback hidden aria-live="polite" role="status">
|
||||
{config.fallbackText} <a href={config.url} target="_blank" rel="noopener noreferrer">{config.fallbackLinkText}</a>
|
||||
</p>
|
||||
<noscript>
|
||||
<p class="jotform-iframe__fallback jotform-iframe__fallback--noscript">
|
||||
{config.fallbackText} <a href={config.url} target="_blank" rel="noopener noreferrer">{config.fallbackLinkText}</a>
|
||||
</p>
|
||||
</noscript>
|
||||
</section>
|
||||
|
||||
<script is:inline>
|
||||
// A frame `load` only proves that the browser completed a document load. It
|
||||
// cannot prove that cross-origin Jotform initialized correctly or accepted
|
||||
// a submission, so this controls the hosted-form fallback only.
|
||||
(() => {
|
||||
const fallbackTimeoutMs = 15_000;
|
||||
|
||||
const initialize = (container) => {
|
||||
if (container.dataset.jotformFallbackInitialized === 'true') return;
|
||||
|
||||
const frame = container.querySelector('[data-jotform-iframe-frame]');
|
||||
const fallback = container.querySelector('[data-jotform-iframe-fallback]');
|
||||
if (!(frame instanceof HTMLIFrameElement) || !(fallback instanceof HTMLElement)) return;
|
||||
|
||||
container.dataset.jotformFallbackInitialized = 'true';
|
||||
fallback.hidden = true;
|
||||
|
||||
let disposed = false;
|
||||
const hideFallback = () => {
|
||||
fallback.hidden = true;
|
||||
container.dataset.jotformFallback = 'hidden';
|
||||
};
|
||||
const showFallback = () => {
|
||||
if (disposed) return;
|
||||
fallback.hidden = false;
|
||||
container.dataset.jotformFallback = 'visible';
|
||||
};
|
||||
const timer = window.setTimeout(showFallback, fallbackTimeoutMs);
|
||||
const onLoad = () => {
|
||||
window.clearTimeout(timer);
|
||||
// A late frame load supersedes the transport timeout. It is still not
|
||||
// treated as a success, conversion, or cross-origin health signal.
|
||||
hideFallback();
|
||||
};
|
||||
const dispose = () => {
|
||||
disposed = true;
|
||||
window.clearTimeout(timer);
|
||||
frame.removeEventListener('load', onLoad);
|
||||
};
|
||||
|
||||
frame.addEventListener('load', onLoad, { once: true });
|
||||
window.addEventListener('pagehide', dispose, { once: true });
|
||||
document.addEventListener('astro:before-swap', dispose, { once: true });
|
||||
};
|
||||
|
||||
document.querySelectorAll('[data-jotform-form-a]').forEach(initialize);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Keep adapter styling route-local: the campaign and main-site bundles must not share campaign CSS. -->
|
||||
<style is:inline>
|
||||
.jotform-iframe {
|
||||
@@ -99,6 +144,10 @@ const fallbackMarkup = `<p class="jotform-iframe__fallback">${escapeHtml(config.
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
.jotform-iframe__fallback[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.jotform-iframe__description {
|
||||
color: var(--color-muted);
|
||||
font-size: .9rem;
|
||||
|
||||
@@ -27,6 +27,7 @@ export type FormAIframeReason =
|
||||
| 'enabled'
|
||||
| 'mode-not-isolated-test'
|
||||
| 'isolated-test-not-approved'
|
||||
| 'live-approval-not-disabled'
|
||||
| 'invalid-environment-role'
|
||||
| 'indexing-enabled'
|
||||
| 'route-not-authorized'
|
||||
@@ -132,6 +133,7 @@ export function resolveFormAIframe(options: ResolveFormAIframeOptions): FormAIfr
|
||||
});
|
||||
const config = resolveFormMode(options.rawMode, options.liveApproved);
|
||||
if (config.mode !== 'isolated-test') return disabled('mode-not-isolated-test');
|
||||
if (options.liveApproved?.trim().toLowerCase() !== 'false') return disabled('live-approval-not-disabled');
|
||||
if (!isTrue(options.isolatedTestApproved)) return disabled('isolated-test-not-approved');
|
||||
if (options.environmentRole?.trim().toLowerCase() !== isolatedTestEnvironmentRole) return disabled('invalid-environment-role');
|
||||
if (isTrue(options.allowIndexing)) return disabled('indexing-enabled');
|
||||
|
||||
@@ -33,7 +33,7 @@ const routes = [
|
||||
];
|
||||
const widths = [320, 390, 768, 1440];
|
||||
const output = await fs.mkdtemp('/tmp/aia-form-a-iframe-');
|
||||
const evidence = { mode, routes, widths, checks: 0, blocked: [], jotformAttempts: [], screenshots: [], status: 'failed' };
|
||||
const evidence = { mode, routes, widths, checks: 0, fallbackChecks: [], blocked: [], jotformAttempts: [], screenshots: [], status: 'failed' };
|
||||
const reserve = net.createServer();
|
||||
await new Promise((resolve, reject) => { reserve.once('error', reject); reserve.listen(0, '127.0.0.1', resolve); });
|
||||
const port = reserve.address().port;
|
||||
@@ -103,12 +103,15 @@ try {
|
||||
const adapterBox = await page.locator('[data-jotform-form-a]').boundingBox();
|
||||
assert.ok(copyBox && adapterBox && Math.abs(copyBox.width - adapterBox.width) <= 1, `${route} should use the full desktop grid column`);
|
||||
}
|
||||
const fallbackMarkup = await iframe.evaluate((element) => element.innerHTML);
|
||||
assert.match(fallbackMarkup, /class="jotform-iframe__fallback"/, route);
|
||||
const encodedFallbackUrl = expectedUrl.replace(/&/g, '&');
|
||||
assert.match(fallbackMarkup, new RegExp(`href="${encodedFallbackUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`), route);
|
||||
assert.match(fallbackMarkup, /target="_blank" rel="noopener noreferrer"/, route);
|
||||
assert.equal(await page.locator('[data-jotform-form-a] > .jotform-iframe__fallback').count(), 0, `${route} should not show a duplicate parent fallback`);
|
||||
const fallback = page.locator('[data-jotform-form-a] > [data-jotform-iframe-fallback]');
|
||||
assert.equal(await fallback.count(), 1, `${route} should render one parent fallback`);
|
||||
assert.equal(await fallback.getAttribute('hidden'), '', `${route} fallback should not reserve space before timeout`);
|
||||
assert.equal(await fallback.getAttribute('aria-live'), 'polite', route);
|
||||
assert.equal(await fallback.locator('a').getAttribute('href'), expectedUrl, route);
|
||||
assert.equal(await fallback.locator('a').getAttribute('target'), '_blank', route);
|
||||
assert.equal(await fallback.locator('a').getAttribute('rel'), 'noopener noreferrer', route);
|
||||
assert.equal(await iframe.evaluate((element) => element.innerHTML.trim()), '', `${route} should not keep fallback content inside the iframe`);
|
||||
assert.equal(await page.locator('[data-jotform-form-a] .jotform-iframe__fallback--noscript').count(), 0, `${route} should not render a duplicate noscript fallback while JavaScript is enabled`);
|
||||
if (route.startsWith('/lp/')) {
|
||||
const expectedSurface = route === '/lp/early-intervention'
|
||||
? '#consultation.lead-form > [data-jotform-form-a].jotform-iframe--embedded'
|
||||
@@ -143,17 +146,24 @@ try {
|
||||
if (url.origin === 'https://form.jotform.com') evidence.jotformAttempts.push(item);
|
||||
return route.abort();
|
||||
});
|
||||
for (const { route } of routes) {
|
||||
for (const { route, locale } of routes) {
|
||||
evidence.checks++;
|
||||
const page = await context.newPage();
|
||||
const response = await page.goto(origin + route, { waitUntil: 'domcontentloaded', timeout: 15000 });
|
||||
assert.equal(response?.status(), 200, `${route} without JavaScript`);
|
||||
assert.equal(await page.locator('[data-jotform-form-a] iframe').count(), 1, `${route} without JavaScript`);
|
||||
const iframe = page.locator('[data-jotform-form-a] iframe');
|
||||
const fallbackMarkup = await iframe.evaluate((element) => element.innerHTML);
|
||||
assert.match(fallbackMarkup, /class="jotform-iframe__fallback"/, `${route} without JavaScript`);
|
||||
assert.match(fallbackMarkup, /target="_blank" rel="noopener noreferrer"/, `${route} without JavaScript`);
|
||||
assert.equal(await page.locator('[data-jotform-form-a] > .jotform-iframe__fallback').count(), 0, `${route} without JavaScript should not show a duplicate parent fallback`);
|
||||
assert.equal(await iframe.evaluate((element) => element.innerHTML.trim()), '', `${route} without JavaScript should not place fallback content inside the iframe`);
|
||||
const noscriptFallback = page.locator('[data-jotform-form-a] .jotform-iframe__fallback--noscript');
|
||||
assert.equal(await noscriptFallback.count(), 1, `${route} without JavaScript should expose one localized hosted-form fallback`);
|
||||
assert.equal(await noscriptFallback.isVisible(), true, `${route} without JavaScript fallback should be visible`);
|
||||
assert.equal(await noscriptFallback.locator('a').getAttribute('target'), '_blank', `${route} without JavaScript`);
|
||||
assert.equal(await noscriptFallback.locator('a').getAttribute('rel'), 'noopener noreferrer', `${route} without JavaScript`);
|
||||
assert.match(
|
||||
await noscriptFallback.innerText(),
|
||||
locale === 'es' ? /Si el formulario integrado no se carga,/ : /If the embedded form does not load,/,
|
||||
`${route} without JavaScript should use the localized fallback`,
|
||||
);
|
||||
if (route.startsWith('/lp/')) {
|
||||
const expectedSurface = route === '/lp/early-intervention'
|
||||
? '#consultation.lead-form > [data-jotform-form-a].jotform-iframe--embedded'
|
||||
@@ -165,6 +175,80 @@ try {
|
||||
await context.close();
|
||||
}
|
||||
|
||||
if (expectedIframe) {
|
||||
// Exercise the first-party fallback with a local fixture. Jotform is never
|
||||
// contacted: every frame request is fulfilled or held inside Playwright.
|
||||
let fallbackScenario = 'immediate';
|
||||
let releaseLateFrame;
|
||||
let timeoutFrameRequested;
|
||||
const context = await browser.newContext({ viewport: { width: 390, height: 844 }, serviceWorkers: 'block' });
|
||||
await context.route('**/*', async (route) => {
|
||||
const request = route.request();
|
||||
const url = new URL(request.url());
|
||||
if (url.origin === origin && ['GET', 'HEAD'].includes(request.method())) return route.continue();
|
||||
const item = { origin: url.origin, path: url.pathname, search: url.search, method: request.method(), fixture: true };
|
||||
evidence.blocked.push(item);
|
||||
if (url.origin === 'https://form.jotform.com' && url.pathname === '/262946078314059') {
|
||||
evidence.jotformAttempts.push(item);
|
||||
if (fallbackScenario === 'delayed') await delay(30);
|
||||
if (fallbackScenario === 'timeout-late') {
|
||||
timeoutFrameRequested?.();
|
||||
await new Promise((resolve) => { releaseLateFrame = resolve; });
|
||||
}
|
||||
return route.fulfill({ contentType: 'text/html', body: '<!doctype html><title>Local Form A Fixture</title><p>Fixture only</p>' });
|
||||
}
|
||||
return route.abort();
|
||||
});
|
||||
|
||||
const openFallbackScenario = async (name) => {
|
||||
const page = await context.newPage();
|
||||
// Keep the production 15-second timeout literal while making this local
|
||||
// fixture deterministic and fast. The override is scoped to the page.
|
||||
await page.addInitScript(() => {
|
||||
const originalSetTimeout = window.setTimeout.bind(window);
|
||||
window.setTimeout = (callback, timeout, ...args) => originalSetTimeout(callback, timeout === 15_000 ? 80 : timeout, ...args);
|
||||
});
|
||||
const response = await page.goto(origin + '/contact', { waitUntil: 'domcontentloaded', timeout: 15000 });
|
||||
assert.equal(response?.status(), 200, `fallback ${name} outer page`);
|
||||
return { page, fallback: page.locator('[data-jotform-iframe-fallback]') };
|
||||
};
|
||||
|
||||
fallbackScenario = 'immediate';
|
||||
{
|
||||
const { page, fallback } = await openFallbackScenario('immediate');
|
||||
await page.waitForTimeout(30);
|
||||
assert.equal(await fallback.isVisible(), false, 'A successful iframe load keeps fallback hidden');
|
||||
evidence.fallbackChecks.push('successful-load-hidden');
|
||||
await page.close();
|
||||
}
|
||||
|
||||
fallbackScenario = 'delayed';
|
||||
{
|
||||
const { page, fallback } = await openFallbackScenario('delayed');
|
||||
await page.waitForTimeout(45);
|
||||
assert.equal(await fallback.isVisible(), false, 'A delayed iframe load stays hidden before the 15-second timeout');
|
||||
evidence.fallbackChecks.push('delayed-load-hidden-before-timeout');
|
||||
await page.close();
|
||||
}
|
||||
|
||||
fallbackScenario = 'timeout-late';
|
||||
{
|
||||
const requested = new Promise((resolve) => { timeoutFrameRequested = resolve; });
|
||||
const { page, fallback } = await openFallbackScenario('timeout-late');
|
||||
await requested;
|
||||
await page.waitForTimeout(100);
|
||||
assert.equal(await fallback.isVisible(), true, 'A missing iframe load reveals the accessible hosted-form fallback after timeout');
|
||||
assert.equal(await fallback.getAttribute('role'), 'status');
|
||||
releaseLateFrame?.();
|
||||
await page.waitForFunction(() => document.querySelector('[data-jotform-iframe-fallback]')?.hidden === true);
|
||||
assert.equal(await fallback.isVisible(), false, 'A late iframe load hides the timeout fallback consistently');
|
||||
evidence.fallbackChecks.push('timeout-visible', 'late-load-hidden');
|
||||
await page.close();
|
||||
}
|
||||
|
||||
await context.close();
|
||||
}
|
||||
|
||||
const formAttempts = evidence.jotformAttempts;
|
||||
if (expectedIframe) {
|
||||
assert.ok(formAttempts.length >= routes.length, 'Expected intercepted Jotform iframe GET attempts');
|
||||
|
||||
@@ -37,6 +37,12 @@ test('Form A iframe uses only fixed, approved route parameters', () => {
|
||||
buildFormAIframeUrl('en', 'landing'),
|
||||
'https://form.jotform.com/262946078314059?formContext=landing&language=en',
|
||||
);
|
||||
const english = resolveFormAIframe(isolated());
|
||||
const spanish = resolveFormAIframe(isolated({ route: '/es/contact', locale: 'es' }));
|
||||
assert.equal(english.fallbackText, 'If the embedded form does not load,');
|
||||
assert.equal(english.fallbackLinkText, 'open the form in a new tab.');
|
||||
assert.equal(spanish.fallbackText, 'Si el formulario integrado no se carga,');
|
||||
assert.equal(spanish.fallbackLinkText, 'abra el formulario en una pestaña nueva.');
|
||||
assert.deepEqual(formAIframePlacements.map((placement) => placement.route), [
|
||||
'/contact',
|
||||
'/es/contact',
|
||||
@@ -56,6 +62,8 @@ test('Form A iframe requires every isolated-test gate and refuses live mode', ()
|
||||
assert.equal(resolveFormAIframe(isolated({ rawMode: undefined })).reason, 'mode-not-isolated-test');
|
||||
assert.equal(resolveFormAIframe(isolated({ rawMode: 'mock' })).reason, 'mode-not-isolated-test');
|
||||
assert.equal(resolveFormAIframe(isolated({ rawMode: 'live', liveApproved: 'true' })).reason, 'mode-not-isolated-test');
|
||||
assert.equal(resolveFormAIframe(isolated({ liveApproved: undefined })).reason, 'live-approval-not-disabled');
|
||||
assert.equal(resolveFormAIframe(isolated({ liveApproved: 'true' })).reason, 'live-approval-not-disabled');
|
||||
assert.equal(resolveFormAIframe(isolated({ isolatedTestApproved: 'false' })).reason, 'isolated-test-not-approved');
|
||||
assert.equal(resolveFormAIframe(isolated({ environmentRole: 'staging' })).reason, 'invalid-environment-role');
|
||||
assert.equal(resolveFormAIframe(isolated({ allowIndexing: 'true' })).reason, 'indexing-enabled');
|
||||
@@ -90,10 +98,13 @@ test('Only the seven allowlisted placements can render the adapter, with no help
|
||||
assert.match(earlyIntervention, /JotformIframe/);
|
||||
assert.match(adapter, /referrerpolicy="no-referrer"/);
|
||||
assert.match(adapter, /sandbox="allow-forms allow-scripts allow-same-origin"/);
|
||||
assert.match(adapter, /const fallbackMarkup = `<p class="jotform-iframe__fallback">/);
|
||||
assert.match(adapter, /set:html=\{fallbackMarkup\}/);
|
||||
assert.match(adapter, /escapeHtml\(config\.url\)/);
|
||||
assert.doesNotMatch(adapter, /<\/h3>\s*(?:\{description[^\n]*\}\s*)?<p class="jotform-iframe__fallback">/);
|
||||
assert.match(adapter, /data-jotform-iframe-frame/);
|
||||
assert.match(adapter, /data-jotform-iframe-fallback hidden aria-live="polite" role="status"/);
|
||||
assert.match(adapter, /<noscript>/);
|
||||
assert.match(adapter, /fallbackTimeoutMs = 15_000/);
|
||||
assert.match(adapter, /frame\.addEventListener\('load', onLoad, \{ once: true \}\)/);
|
||||
assert.match(adapter, /document\.addEventListener\('astro:before-swap', dispose, \{ once: true \}\)/);
|
||||
assert.doesNotMatch(adapter, /set:html=|fallbackMarkup|escapeHtml\(/);
|
||||
assert.match(adapter, /target="_blank" rel="noopener noreferrer"/);
|
||||
assert.match(adapter, /--jotform-frame-height: 60rem/);
|
||||
assert.match(adapter, /max-width: none;/);
|
||||
@@ -101,7 +112,7 @@ test('Only the seven allowlisted placements can render the adapter, with no help
|
||||
assert.match(adapter, /\.jotform-iframe--embedded \{\s*--jotform-frame-height: 42rem/);
|
||||
assert.match(adapter, /\.jotform-iframe__frame \{\s*height: 68rem/);
|
||||
assert.match(adapter, /\.jotform-iframe--embedded \.jotform-iframe__frame \{\s*height: 50rem/);
|
||||
assert.doesNotMatch(adapter, /<script|onload=|postMessage|fetch\(|form\.submit|analytics|googletag/i);
|
||||
assert.doesNotMatch(adapter, /for-form-embed-handler|jotformEmbedHandler|onload=|postMessage|fetch\(|form\.submit|analytics|googletag/i);
|
||||
assert.doesNotMatch(campaign, /262946078314059/);
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user