Two bugs surfaced on first dev-server test:
1. /api/staff/file 404s for valid file ids. The old per-org check
read civicrm_entity_file and required entity_id==orgId, but our
upload route anchors files to the submitter's contact id, not the
org's — the entity_file row is metadata-only on this install
(see comment in app/api/upload/route.ts). The custom-field column
is the real ownership signal, which /api/staff/report already uses,
and the staff key already gates org access. Drop the bogus check;
keep the entity_table whitelist as defence.
2. Same-origin PDF iframe blocked by frame-ancestors 'none'. The
strict global CSP excludes /staff/report; add /api/staff/file to
the same embed-friendly profile so the lightbox iframe can load.
Also move the sandbox/default-src 'none' CSP to the attachment path
only — a strict sandbox header breaks Chrome's PDF viewer on inline
responses (it needs to load fonts and plugin-mode rendering). On
inline we rely on the SAFE_INLINE_MIMES allowlist + X-Content-Type-
Options + the app's global CSP.
App side:
- Per-route CSP: /staff/report now sets frame-ancestors 'self'
<CIVI_BASE_URL origin> and drops X-Frame-Options so the CiviCRM
extension can iframe it. All other routes keep frame-ancestors
'none' + X-Frame-Options: DENY via a path-negation source.
- Staff page recognises ?frame=1 and renders without SiteHeader/
SiteFooter so it fills the iframe cleanly.
- StaffReportView posts its scrollHeight to the parent window via
postMessage when framed; the Civi tab listens and auto-resizes
the iframe (no nested scrollbar). Anchor strip drops its sticky
positioning in frame mode since there's no internal scroll.
CiviCRM extension (civi-extension/webform-mw/, key webform-mw):
- info.xml + main hook file (webform_mw.php) implementing
hook_civicrm_tabset to add an 'Engagement Report' tab to
Organization contact-view pages.
- CRM/WebformMw/Page/Tab.php + Smarty template render an iframe
pointing at <WEBFORM_MW_APP_URL>/staff/report?org=<cid>&key=&frame=1,
with a postMessage listener that validates event.origin against
the configured app URL before resizing.
- Config via PHP constants in civicrm.settings.php (WEBFORM_MW_APP_URL,
WEBFORM_MW_STAFF_KEY) or matching env vars. Help banner shown when
unconfigured.
- README documents install, config, behaviour, security caveats.
Two package-lock.json files exist (parent civi-webform/ + this app);
Next 16 silently picked the outer one, so Turbopack watched the parent
node_modules/, .claude-flow/, .swarm/, ruvector.db. Background writes
in those trees triggered a recompile loop that thrashed .next/dev and
leaked memory until the dev server crashed. Setting turbopack.root
keeps the watcher scoped to WebForm-mw/.
- Initialize Next.js project with Tailwind CSS
- Create CiviCRM APIv4 integration layer
- Implement stage-based form visibility (stages 0-5)
- Add field mapping configuration for CRM-to-form linking
- Create API routes for data retrieval and submission
- Record form submissions as CiviCRM activities
- Support dynamic contactId and orgId via URL parameters
- Ensure robust form state management with react-hook-form
Co-authored-by: joelbrock <52835+joelbrock@users.noreply.github.com>