Skip to content

perf(trustlab): preload Airtable form embeds so dialogs open instantly#1496

Merged
kelvinkipruto merged 4 commits into
mainfrom
ft/trustlab-preload
Jul 14, 2026
Merged

perf(trustlab): preload Airtable form embeds so dialogs open instantly#1496
kelvinkipruto merged 4 commits into
mainfrom
ft/trustlab-preload

Conversation

@kelvinkipruto

Copy link
Copy Markdown
Contributor

Description

Airtable form embeds across TrustLab pages (helpline cards, row cards, and action banners) only started loading when a user clicked the button to open the dialog. Since an Airtable embed boots the entire Airtable app inside an iframe, users stared at a blank dialog for several seconds before the form appeared.

Changes

  • Preload on page load — embeds now start loading in the background once the browser is idle after the page renders, so the form is typically ready before the user clicks. Loading is deferred to idle time so it doesn't compete with the page's own initial load.
  • Loading skeleton — the embed dialog shows a skeleton until the iframe finishes loading, instead of a blank white area, with the form fading in once ready.
  • Warm connections — added preconnect/dns-prefetch hints for Airtable domains to shave DNS/TLS handshake time off the first request.
  • Keep embeds alive — dialogs stay mounted after closing, so reopening a form is instant instead of reloading it from scratch.
  • The embed markup is now parsed and rendered as a controlled iframe (shared component) rather than injected as raw HTML.

Fixes # (issue)

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Screenshots

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation

create reusable AirtableEmbed component with skeleton loading state
add useAirtableEmbedPreload hook to preload embeds during browser idle time to avoid blocking initial page load
add dns-prefetch and preconnect links for airtable domains in document head
update HelplineCard, RowCard, and ActionBanner to use the new embed component and preload logic, replacing raw HTML embed injection
add keepMounted to embed dialogs to preserve component state when closed
@kilemensi

Copy link
Copy Markdown
Member

@codex review

chatgpt-codex-connector[bot]

This comment was marked as resolved.

pasted embed code's iframe src attributes are html-escaped; decode entities before reusing the extracted url as the iframe src
@kelvinkipruto

Copy link
Copy Markdown
Contributor Author

@codex @claude Review

@claude

claude Bot commented Jul 14, 2026

Copy link
Copy Markdown

Claude finished @kelvinkipruto's task in 3m 7s —— View job


Claude finished reviewing @kelvinkipruto's PR

Todo list

  • Gather context (read diff, changed files)
  • Review AirtableEmbed.js (parsing, iframe src, security)
  • Review useAirtableEmbedPreload.js (idle preload logic)
  • Review HelplineCard, HelplineEmbedDialog, RowCard, ActionBanner changes
  • Review _document.page.js preconnect hints
  • Post final review

Summary

Nice, well-scoped perf improvement. The idle-time preload, keep-mounted dialogs, and skeleton loading state are all reasonable, minimal implementations of what the description promises.

Codex's finding — already fixed ✅

The HTML-entity decoding issue Codex flagged (AirtableEmbed.js) was addressed in the latest commit aa0ae64 (decodeHtmlEntities), which now correctly unescapes &, <, >, ", ' before assigning the extracted URL to src. Confirmed by reading the current file — the fix is in place.

Additional observations

  1. apps/trustlab/src/components/AirtableEmbed/AirtableEmbed.js:33-53 — fallback path bypasses all the new perf/UX work. When extractIframeSrc can't find a src attribute (regex miss), the component falls back to the old dangerouslySetInnerHTML behavior. That path gets none of the benefits this PR adds: no skeleton, no load/preload gating (it renders unconditionally whenever load is true, immediately booting the Airtable iframe even before the dialog opens), and no fade-in. Given embedCode comes from a trusted Payload "code" field admins paste from Airtable's own embed generator (apps/trustlab/src/payload/blocks/Helplines.js:63-69), this fallback should rarely trigger — but if it does (e.g. an editor tweaks the markup or Airtable changes its snippet format), the page silently regresses to the old blank-dialog behavior with no indication anything went wrong. Worth at least a code comment noting this is a "best effort" fallback, or logging when it's hit, so a future embed-format change doesn't silently degrade UX.

  2. AirtableEmbed.js:22 regex is reasonably targeted but fragile by nature (parsing HTML with regex). It works for Airtable's standard <iframe ... src="..."> snippet, but relies on src being preceded by whitespace and the value being quote-delimited — fine for the known input shape, just flagging it's not a general HTML parser (a DOMParser-based extraction would be more robust, though probably overkill for this trusted, well-known input).

  3. HelplineCard.js / RowCard.js — nice bundled fix: both now only mount HelplineEmbedDialog when hasEmbed is true (previously always mounted, even with no embedCode). This is a good cleanup but is technically unrelated to the perf goal of the PR — worth calling out in the PR description since it changes rendered output for cards without an embed.

  4. Minor/nitHelplineEmbedDialog.js sets sx={{ p: 0 }} on DialogContent so the iframe/skeleton fills edge-to-edge, but ActionBanner.js's inline Dialog/DialogContent (lines 150-159) doesn't, so the skeleton and iframe will have default Dialog padding around them there. Not a regression (matches pre-PR behavior) but a small inconsistency between the two embed surfaces now that both go through the same AirtableEmbed component.

Not verified

I wasn't able to run pnpm lint:check / pnpm jest --filter=trustlab in this sandbox — bash commands beyond simple reads require interactive approval that isn't available in this run. Everything above is from static reading of the diff and surrounding code; please run the lint/test suite before merging (there are no committed snapshots for HelplineCard/RowCard/ActionBanner tests, so no snapshot regen should be needed, but worth confirming behaviorally).

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: aa0ae64891

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@kelvinkipruto
kelvinkipruto added this pull request to the merge queue Jul 14, 2026
Merged via the queue into main with commit 6d49122 Jul 14, 2026
8 checks passed
@kelvinkipruto
kelvinkipruto deleted the ft/trustlab-preload branch July 14, 2026 09:14
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.

3 participants