Skip to content

fix(layout,splash): enforce global chrome across /lighthouse + overhaul first-visit splash logic#39

Open
devin-ai-integration[bot] wants to merge 9 commits into
mainfrom
devin/1777566257-global-layout-splash-fix
Open

fix(layout,splash): enforce global chrome across /lighthouse + overhaul first-visit splash logic#39
devin-ai-integration[bot] wants to merge 9 commits into
mainfrom
devin/1777566257-global-layout-splash-fix

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Two related UX bugs, one PR.

1. Global layout chrome (incl. /lighthouse)

/lighthouse was rendering BrandHeader / BrandFooter / SiteContactDrawer / a bespoke CookieConsentBanner directly in its own layout.tsx, which meant it diverged from the rest of the marketing site (no sticky site-chrome-sticky wrapper, no mobile-nav overlay, a different cookie banner, different footer metadata surface, etc.).

  • apps/web/src/app/lighthouse/layout.tsx is now a thin segment wrapper — metadata + fonts + LighthouseJsonLd + LighthouseTechFingerprints only.
  • apps/web/src/app/lighthouse/page.tsx and apps/web/src/app/lighthouse/report/[id]/page.tsx wrap their content in the shared MarketingChrome, so lighthouse now gets the exact same sticky brand header (with the gold trim rule), mobile-nav overlay, reach-out dialog, site-body-canvas, BrandFooter, cookie consent banner, and GTM/analytics scripts as (site)/*.
  • MarketingChrome gained four optional props so lighthouse can keep its "audit" chip in the header and its "Lighthouse Scanner v2 · Powered by Gemini / ElysiaJS / Next.js" attribution in the footer without forking the component:
    • headerCurrentSection?: "audit"
    • footerBuildTag?: string
    • footerPoweredBy?: ReadonlyArray<{ label; href }>
    • segmentClassName?: string
  • /lighthouse/report/[id]/print is intentionally left outside MarketingChrome because the existing global @media print rules in layout-shell.css hide .site-body-canvas — keeping PDF output clean.

The gold trim (.dba-header-rule) lives inside BrandHeader and its CSS is already loaded globally via dba-global.cssbrand-chrome.css, so consolidating every page onto MarketingChrome is what actually makes the trim render consistently everywhere.

2. First-visit splash popup (critical bug)

The splash was mounted inside HomePage (apps/web/src/components/marketing/HomePage.tsx), so it only existed in the React tree when the user rendered /. A visitor who landed on /lighthouse, /services/*, /contact, /tools, etc. as their first URL never saw the splash; it only fired on their second visit (when they navigated to /).

  • FirstVisitSplash is now mounted once in the root layout (apps/web/src/app/layout.tsx), wrapped in a <Suspense fallback={null}> because useSearchParams forces the subtree onto the client.
  • Removed the stale <FirstVisitSplash /> render from HomePage.
  • Splash gating logic rewritten:
    • Bumped storage key dba_first_visit_shown_v1dba_first_visit_shown_v2 so existing visitors get evaluated once under the new rules.
    • localStorage access is wrapped in try/catch for private-mode / storage-disabled browsers.
    • Entry is classified as micro-saas (and the splash is skipped + marked shown) when:
      • pathname === "/tools" or pathname.startsWith("/tools/"), or
      • any of ref, source, utm_source, utm_campaign, utm_medium, utm_content search-param values match /micro[-_ ]?saas|microsaas/i.
    • All other first-time entries (from any URL) see the splash once, after a 1.5s delay.
    • The effect runs only on mount — deliberately not re-firing on client-side navigation (documented with a scoped biome-ignore for useExhaustiveDependencies).

Review & Testing Checklist for Human

  • Hit each of these first, in a fresh incognito window (localStorage cleared) and confirm splash fires once ~1.5s after load and stays dismissed on further navigation: /, /lighthouse, /contact, /services/local-seo, an arbitrary marketing slug like /services/custom-web-design.
  • Hit /tools first in a fresh incognito window → splash must not appear; then navigate to / → splash must still not appear (micro-saas entry sticks for that visitor).
  • Hit /?utm_campaign=microsaas_launch first in a fresh incognito window → splash must not appear.
  • Compare the /lighthouse page side-by-side with / and /tools: same sticky header, same gold trim under the header, same footer, same reach-out sticky button, same cookie consent banner. The only intentional differences are the "audit" chip in the header and the "Powered by Gemini / ElysiaJS / Next.js" row in the footer.
  • Open a magic-link report URL /lighthouse/report/<id> — same chrome as above should render around the report, and the "Print / Save as PDF" CTA should still land on a clean /print page with no marketing chrome.

Notes

  • The only CI lint diagnostic remaining is a pre-existing biome formatter warning on apps/web/src/components/JsonLd.tsx that is unrelated to this PR (confirmed by running bun run lint on main before any of these changes).
  • bun run typecheck and bun run build both pass locally against this branch.

Link to Devin session: https://app.devin.ai/sessions/eb3d9c0a8a374727b60829b239ae5835
Requested by: @Designed-By-Anthony


Open in Devin Review

…-visit splash

- Move FirstVisitSplash mount from HomePage to root layout so it fires
  on the user's very first landing regardless of entry URL (fixes the
  'only shows on the second visit to /' bug).
- Gate the splash behind a micro-saas entry-point check: suppress on
  /tools* and on any utm/ref/source/utm_campaign value matching
  /micro[-_ ]?saas|microsaas/i. Bump storage key to v2 so existing
  visitors evaluate the new rules once.
- Bring /lighthouse in line with the global marketing chrome: its
  layout no longer renders BrandHeader/BrandFooter/SiteContactDrawer
  directly. Instead the lighthouse audit and report-viewer pages wrap
  their content in MarketingChrome, which guarantees the same sticky
  header, mobile nav, gold trim, reach-out dialog, and BrandFooter as
  the rest of the site.
- Extend MarketingChrome with optional headerCurrentSection /
  footerBuildTag / footerPoweredBy / segmentClassName props so the
  lighthouse surface can keep its 'audit' chip and Scanner v2 /
  Powered-by attribution inside the shared chrome.
- /lighthouse/report/[id]/print stays intentionally outside
  MarketingChrome to keep the PDF output clean.

Co-Authored-By: Anthony Jones <anthony@designedbyanthony.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Apr 30, 2026

Copy link
Copy Markdown

Deploying designedbyanthony with  Cloudflare Pages  Cloudflare Pages

Latest commit: f6d1984
Status: ✅  Deploy successful!
Preview URL: https://46f6e162.dba-92r.pages.dev
Branch Preview URL: https://devin-1777566257-global-layo.dba-92r.pages.dev

View logs

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 5 additional findings.

Open in Devin Review

Merge origin/main into branch. Resolves conflicts in:
- apps/web/src/app/layout.tsx (drop layout-shell.css import; combine
  font setup with global FirstVisitSplash mount)
- apps/web/src/components/marketing/MarketingChrome.tsx (keep main's
  inline-Tailwind sticky-header chrome AND new optional props for
  segment customization on /lighthouse).

Also bumps FirstVisitSplash to v3 storage key and reads window.location
directly in useEffect instead of useSearchParams, eliminating the
Suspense/hydration race that prevented the splash from firing on the
user's true first paint.

Co-Authored-By: Anthony Jones <anthony@designedbyanthony.com>
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 2 commits May 1, 2026 00:32
Mounting the splash globally in the root layout caused it to fire on
the print sub-route, which is intentionally bare for clean PDF output.
Skip without persisting the flag so normal subsequent visits still
trigger the splash on first paint.

Co-Authored-By: Anthony Jones <anthony@designedbyanthony.com>
…ite) group)

Main moved /lighthouse and /lighthouse/report routes from
apps/web/src/app/lighthouse/ into the (site) route group at
apps/web/src/app/(site)/lighthouse/. Accept main's relocation and
delete my old lighthouse/layout.tsx + lighthouse/page.tsx that were
superseded by the (site)/lighthouse/page.tsx wrapper which already
uses MarketingChrome.

Splash v3 changes (drop useSearchParams + PRINT_PATH_PATTERN guard)
remain intact on this branch.

Co-Authored-By: Anthony Jones <anthony@designedbyanthony.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…e) move

Phase 1A on main moved /lighthouse routes into the (site) route group
but introduced two bugs that Devin Review flagged:

1. lighthouse-globals.css (~2000 LOC of audit UI styling) lost its
   only importer when apps/web/src/app/lighthouse/layout.tsx was
   deleted. The entire scanner UI was rendering unstyled.
   Add a new apps/web/src/app/(site)/lighthouse/layout.tsx that
   imports the relocated CSS and keeps LighthouseJsonLd /
   LighthouseTechFingerprints + segment metadata.

2. (site)/lighthouse/page.tsx wraps content in <MarketingChrome>
   which already renders <main id="main-content">, but the page
   then nested a second <main id="main-content"> inside it —
   duplicate landmark IDs, invalid HTML, broke screen-reader nav
   and Playwright #main-content selectors. Replace the nested
   <main> with a plain <div>.

Also:
- Re-attach the audit chip + Lighthouse Scanner v2 footer + Powered-by
  attributions via the optional MarketingChrome props introduced
  earlier in this PR.
- Add segmentClassName="lighthouse-segment" so CSS rules scoped
  to .lighthouse-segment (~10 occurrences) actually apply.
- Fix the relative @import paths in lighthouse-globals.css (now one
  directory deeper after the (site) move).
- Update the biome.json override include path to match the new CSS
  location so the per-file lint exemptions still take effect.

Co-Authored-By: Anthony Jones <anthony@designedbyanthony.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Devin Review noted the report MarketingChrome wrapper was missing
`segmentClassName="lighthouse-segment"`, so the .lighthouse-segment
scoped CSS rules (padding, layout, score-card overrides) wouldn't
apply on the report viewer route — the main /lighthouse page got
the prop in the previous commit but the report shell was missed.

Co-Authored-By: Anthony Jones <anthony@designedbyanthony.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Devin Review noted my new (site)/lighthouse/layout.tsx exported only
title + description, dropping the canonical URL, openGraph block,
twitter cards, robots directives, keywords, category, viewport, and
appleWebApp config that the deleted apps/web/src/app/lighthouse/layout.tsx
had. Next merges parent metadata partially (title/description) but
fully replaces openGraph and twitter blocks, so leaving them out
silently broke OG image, OG URL, twitter:image, and the
`<link rel="canonical">` for /lighthouse.

Restore the full metadata + viewport export from the pre-Phase-1A
layout, including alternates.canonical pointing at absoluteSiteUrl("/lighthouse").

Co-Authored-By: Anthony Jones <anthony@designedbyanthony.com>
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 2 commits May 1, 2026 10:33
Main shipped the next major refactor wave while this PR was open:
- Phase 1B/2/3: AuditResults migration + deliverable bronze + CSS demolition
- SEV-0: purge ghost classes from Lighthouse landing components
- SEV-1: Linear/Stripe canonical glass + display weight downgrade
- SEV-2: script orchestration, JSON-LD, global bronze sweep

That demolished apps/web/src/app/(site)/lighthouse/lighthouse-globals.css
and the segment layout I had added on this branch — main moved fully
to design-system tokens (SECTION_SHELL, SURFACE_CARD_TECHNICAL, etc.)
for lighthouse styling, so the per-segment CSS file and its layout
importer are intentionally gone.

Conflict resolution:
- Accept main's deletion of (site)/lighthouse/layout.tsx and
  (site)/lighthouse/lighthouse-globals.css.
- Accept main's rewritten (site)/lighthouse/page.tsx and
  (site)/lighthouse/report/[id]/page.tsx (canonical design-system
  tokens, no longer needs my old MarketingChrome props).
- KEEP this branch's FirstVisitSplash.tsx (v3 logic that reads
  window.location directly on mount and skips /print) — main had
  reverted to the original v1 HomePage-only logic, but this PR's
  whole point is fixing the splash so we re-apply v3 over main.
- Drop the now-stale biome.json override block that referenced the
  deleted lighthouse-globals.css.
- Fix duplicate `btnPrimaryAudit` import in AuditForm.tsx that
  Git's auto-merge inserted.

The MarketingChrome optional props from earlier in this PR
(headerCurrentSection, footerBuildTag, footerPoweredBy,
segmentClassName) are now unused but stay as harmless extension
points; main's lighthouse pages just don't pass them.

Co-Authored-By: Anthony Jones <anthony@designedbyanthony.com>
Main shipped The Ledger feature suite and several other Phase-4
follow-ups while this PR was open:
- D1 schema/admin app + Stripe webhook ledger pipeline
- Turnstile site-key resolver pulled into AuditForm
- Bespoke config messaging + EN/ES locale switcher + checkout success route

Only one real content conflict: AuditForm.tsx imports — main added
`resolveEffectiveSiteKey` from `@/lib/turnstile` next to the existing
`buildPublicApiUrl` import. Took main's version (both imports kept).

Splash v3 + global FirstVisitSplash mount in root layout survived the
auto-merge intact (verified).

Co-Authored-By: Anthony Jones <anthony@designedbyanthony.com>
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.

1 participant