From 9d608e121fc96a94b0425259c604e99a0c6e3d12 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 17 Sep 2026 15:29:06 -0700 Subject: [PATCH 1/3] fix(landing): repair public links and streamline landing previews --- apps/docs/app/robots.txt/route.ts | 1 - apps/docs/components/footer/footer.tsx | 2 +- .../core-feature-card/core-feature-card.tsx | 1 + .../features-rail/features-rail.test.tsx | 31 +- .../features-rail/features-rail.tsx | 23 +- .../(landing)/components/footer/footer.tsx | 2 +- .../production-workflow-stage.tsx | 66 ++-- .../nav-menu-chip/nav-menu-chip.test.tsx | 18 ++ .../nav-menu-chip/nav-menu-chip.tsx | 14 +- .../site-structured-data.tsx | 2 +- .../interactive-library-folder.tsx | 5 +- apps/sim/app/llms-full.txt/route.ts | 2 +- apps/sim/app/sitemap.test.ts | 54 ++++ apps/sim/app/sitemap.ts | 15 +- apps/sim/lib/content/seo.ts | 2 +- apps/sim/next.config.ts | 2 +- .../tab-strip/tab-strip.dom.test.tsx | 18 ++ .../src/components/tab-strip/tab-strip.tsx | 24 +- packages/workflow-renderer/package.json | 8 + packages/workflow-renderer/src/index.ts | 22 +- .../src/subflow/subflow-node-view.tsx | 2 +- .../workflow-block/workflow-block-view.tsx | 284 +----------------- .../workflow-renderer/src/workflow-type.tsx | 272 +++++++++++++++++ 23 files changed, 527 insertions(+), 343 deletions(-) create mode 100644 apps/sim/app/sitemap.test.ts create mode 100644 packages/workflow-renderer/src/workflow-type.tsx diff --git a/apps/docs/app/robots.txt/route.ts b/apps/docs/app/robots.txt/route.ts index c9f62b347f8..205397a3b1c 100644 --- a/apps/docs/app/robots.txt/route.ts +++ b/apps/docs/app/robots.txt/route.ts @@ -10,7 +10,6 @@ export async function GET() { User-agent: * Disallow: /.next/ Disallow: /api/internal/ -Disallow: /_next/static/ Disallow: /admin/ Allow: / Allow: /llms.txt diff --git a/apps/docs/components/footer/footer.tsx b/apps/docs/components/footer/footer.tsx index 342f0b733ab..0d891170fcf 100644 --- a/apps/docs/components/footer/footer.tsx +++ b/apps/docs/components/footer/footer.tsx @@ -84,7 +84,7 @@ const SOCIAL_LINKS: FooterItem[] = [ { label: 'X (Twitter)', href: 'https://x.com/simdotai', external: true }, { label: 'LinkedIn', - href: 'https://www.linkedin.com/company/simstudioai/', + href: 'https://www.linkedin.com/company/simdotai/', external: true, }, { diff --git a/apps/sim/app/(landing)/components/features/components/core-feature-card/core-feature-card.tsx b/apps/sim/app/(landing)/components/features/components/core-feature-card/core-feature-card.tsx index f5a0bacbedc..f28ee54d6de 100644 --- a/apps/sim/app/(landing)/components/features/components/core-feature-card/core-feature-card.tsx +++ b/apps/sim/app/(landing)/components/features/components/core-feature-card/core-feature-card.tsx @@ -48,6 +48,7 @@ export function CoreFeatureCard({ const graphic = (
{ root = null host?.remove() host = null + vi.unstubAllGlobals() }) function mount(strict = false): HTMLElement { @@ -106,6 +107,34 @@ describe('foldScrollLeft', () => { }) describe('FeaturesRail', () => { + it('waits until the rail approaches the viewport before adding the loop copies', () => { + let notify: IntersectionObserverCallback | undefined + const disconnect = vi.fn() + const observe = vi.fn() + vi.stubGlobal( + 'IntersectionObserver', + class { + constructor(callback: IntersectionObserverCallback) { + notify = callback + } + observe = observe + disconnect = disconnect + } + ) + const rail = mount() + expect(observe).toHaveBeenCalledWith(rail) + expect(rail.children).toHaveLength(3) + const observer = {} as IntersectionObserver + act(() => notify?.([{ isIntersecting: false } as IntersectionObserverEntry], observer)) + expect(rail.children).toHaveLength(3) + act(() => notify?.([{ isIntersecting: true } as IntersectionObserverEntry], observer)) + expect(rail.children).toHaveLength(9) + expect(rail.scrollLeft).toBe(SET) + expect(disconnect).toHaveBeenCalledOnce() + act(() => notify?.([{ isIntersecting: false } as IntersectionObserverEntry], observer)) + expect(rail.children).toHaveLength(9) + }) + it('server-renders the finite rail once, with the scroll chrome', () => { const html = renderToStaticMarkup( {cards()} diff --git a/apps/sim/app/(landing)/components/features/components/features-rail/features-rail.tsx b/apps/sim/app/(landing)/components/features/components/features-rail/features-rail.tsx index c2bcf2058dc..93ef0f0bd67 100644 --- a/apps/sim/app/(landing)/components/features/components/features-rail/features-rail.tsx +++ b/apps/sim/app/(landing)/components/features/components/features-rail/features-rail.tsx @@ -86,8 +86,8 @@ interface FeaturesRailProps { /** Accessible name of the scrolling region. */ label: string /** - * The cards, in order. Each becomes one slot; once JS runs the whole set is - * cloned on both sides so the rail loops. + * The cards, in order. Each becomes one slot; as the rail approaches the + * viewport the whole set is cloned on both sides so the rail loops. */ children: ReactNode } @@ -96,7 +96,7 @@ interface FeaturesRailProps { * The homepage product rail: native horizontal scrolling that never ends. * * The server renders the set once, so the HTML - and any visit without JS - is - * the plain finite rail with the first card under the heading. After hydration + * the plain finite rail with the first card under the heading. Near the viewport * the set is cloned once on each side, the scroll position jumps one set width * before paint so nothing visibly moves (folded, so Strict Mode's second run of * the effect lands on the same spot), and a passive scroll listener folds the @@ -124,7 +124,22 @@ export function FeaturesRail({ label, children }: FeaturesRailProps) { const cards = Children.toArray(children) useEffect(() => { - setLooping(true) + const rail = railRef.current + if (!rail) return + if (typeof IntersectionObserver === 'undefined') { + setLooping(true) + return + } + const observer = new IntersectionObserver( + (entries) => { + if (!entries.some((entry) => entry.isIntersecting)) return + setLooping(true) + observer.disconnect() + }, + { rootMargin: '600px' } + ) + observer.observe(rail) + return () => observer.disconnect() }, []) useLayoutEffect(() => { diff --git a/apps/sim/app/(landing)/components/footer/footer.tsx b/apps/sim/app/(landing)/components/footer/footer.tsx index 44be284c0d4..c05d1f94576 100644 --- a/apps/sim/app/(landing)/components/footer/footer.tsx +++ b/apps/sim/app/(landing)/components/footer/footer.tsx @@ -119,7 +119,7 @@ const SOCIAL_LINKS: FooterItem[] = [ { label: 'X (Twitter)', href: 'https://x.com/simdotai', external: true }, { label: 'LinkedIn', - href: 'https://www.linkedin.com/company/simstudioai/', + href: 'https://www.linkedin.com/company/simdotai/', external: true, }, { diff --git a/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/production-workflow-stage.tsx b/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/production-workflow-stage.tsx index b142adc61ce..2a70760c709 100644 --- a/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/production-workflow-stage.tsx +++ b/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/production-workflow-stage.tsx @@ -75,20 +75,20 @@ const EMPTY_IDS: ReadonlySet = new Set() const ACTION_BUTTON_STYLES = [ 'size-[24px] rounded-md p-0', 'border-none bg-transparent text-[var(--text-icon)]', - 'hover-hover:bg-[var(--surface-5)] hover-hover:!text-[var(--text-primary)]', - 'dark:hover-hover:bg-[var(--surface-4)]', - 'transition-[background-color,color,opacity,transform] duration-150 active:scale-[0.96]', + 'transition-[background-color,color,opacity,transform] duration-150', 'group-data-[node-selected]:text-[var(--surface-2)]', - 'hover-hover:group-data-[node-selected]:bg-[var(--surface-2)]', - 'hover-hover:group-data-[node-selected]:!text-[var(--text-primary)]', ].join(' ') const FIRST_ACTION_STYLES = "!w-[40px] [clip-path:path('M23.75_0A8_8_0_0_0_17.6_2.88L3.41_19.9A2.5_2.5_0_0_0_5.34_24L36_24A4_4_0_0_0_40_20L40_4A4_4_0_0_0_36_0Z')] [&>svg]:translate-x-[8px] [&>svg]:translate-y-px" +/** A 24px target even at MIN_ZOOM, extending above/left of the unchanged 40px painted slot. */ +const RUN_ACTION_HIT_STYLES = + 'group/run relative -ml-[14px] size-[54px] shrink-0 border-none bg-transparent! p-0' + /** The running run slot: graphite fill, inverse glyph - the editor's own treatment. */ const RUNNING_RUN_STYLES = - '!bg-[var(--text-secondary)] !text-[var(--text-inverse)] hover-hover:!bg-[var(--white)] hover-hover:!text-[var(--surface-inverted)]' + '!bg-[var(--text-secondary)] !text-[var(--text-inverse)] group-hover-hover/run:!bg-[var(--white)] group-hover-hover/run:!text-[var(--surface-inverted)]' /** A bystander card's actions dim mid-run; the run/stop slot keeps its ordinary chrome. */ const BYSTANDER_ACTION_STYLES = '!bg-transparent !opacity-25 hover-hover:!bg-transparent dark:hover-hover:!bg-transparent' @@ -227,7 +227,7 @@ function PreviewActionBar({ block, running, workflowRunning, onRunToggle }: Prev return (
{sweeping && ( @@ -250,35 +250,42 @@ function PreviewActionBar({ block, running, workflowRunning, onRunToggle }: Prev )} - + @@ -291,17 +298,14 @@ function PreviewActionBar({ block, running, workflowRunning, onRunToggle }: Prev diff --git a/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/nav-menu-chip.test.tsx b/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/nav-menu-chip.test.tsx index c2523b051cb..37ea2107d22 100644 --- a/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/nav-menu-chip.test.tsx +++ b/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/nav-menu-chip.test.tsx @@ -27,6 +27,13 @@ vi.mock('next/link', () => ({ ), })) +vi.mock('next/dynamic', () => ({ + default: + () => + ({ item }: { item: NavMenuItemData }) => ( + {item.preview.kind} + ), +})) vi.mock('@/app/(landing)/components/chevron-arrow', () => ({ ChevronArrow: () => null, })) @@ -104,6 +111,17 @@ function expectSelected(href: string, kind: string) { } describe('NavMenuCluster feature selection', () => { + it('mounts the preview on first opening and preserves it during the exit transition', () => { + expect(host.querySelector('output')).toBeNull() + hover(element('#nav-platform-menu-trigger')) + expect(host.querySelector('output')).not.toBeNull() + act(() => { + document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })) + }) + expect(element('#primary-navigation-mega-menu').getAttribute('aria-hidden')).toBe('true') + expect(host.querySelector('output')).not.toBeNull() + }) + it('prefetches destinations only while their menu is open', () => { const overview = element('a[href="/platform"]') const customers = element('#nav-customers-menu a[href="/customers/rivian"]') diff --git a/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/nav-menu-chip.tsx b/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/nav-menu-chip.tsx index 80afd4e6fc2..ce6f699f547 100644 --- a/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/nav-menu-chip.tsx +++ b/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/nav-menu-chip.tsx @@ -2,6 +2,7 @@ import { type ReactNode, useEffect, useRef, useState } from 'react' import { ChipChevronDown, chipContentLabelClass, chipVariants, cn } from '@sim/emcn' +import dynamic from 'next/dynamic' import { flushSync } from 'react-dom' import { HOME_INSET, @@ -11,11 +12,18 @@ import { import { NavMenuCard } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-card' import { NavMenuItem } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-item' import { NavMenuLogoMarquee } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-logo-marquee' -import { NavMenuPreview } from '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/nav-menu-preview' import type { NavMenu } from '@/app/(landing)/components/navbar/components/nav-menu-chip/types' import { NAVBAR_GLASS_SURFACE } from '@/app/(landing)/components/navbar/components/navbar-shell' import { useNavbarMenu } from '@/app/(landing)/components/navbar/hooks/use-navbar-menu' +const NavMenuPreview = dynamic( + () => + import( + '@/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/nav-menu-preview' + ).then((module) => module.NavMenuPreview), + { loading: () =>
} +) + interface NavMenuClusterProps { /** Non-empty group of mega-menus that share one stable panel. */ menus: readonly [NavMenu, ...NavMenu[]] @@ -68,6 +76,7 @@ export function NavMenuCluster({ menus, modelsPreview }: NavMenuClusterProps) { () => menus.find((menu) => !isFloating(menu)) ?? menus[0] ) const [activeItem, setActiveItem] = useState(surfaceMenu.sections[0].items[0]) + const [previewMounted, setPreviewMounted] = useState(false) useEffect(() => { if (!open) return @@ -86,6 +95,7 @@ export function NavMenuCluster({ menus, modelsPreview }: NavMenuClusterProps) { const activateMenu = (menu: NavMenu) => { setActiveMenu(menu) if (!isFloating(menu)) { + setPreviewMounted(true) setSurfaceMenu(menu) setActiveItem(menu.sections[0].items[0]) } @@ -270,7 +280,7 @@ export function NavMenuCluster({ menus, modelsPreview }: NavMenuClusterProps) { ))}
- + {previewMounted && }
diff --git a/apps/sim/app/(landing)/components/site-structured-data/site-structured-data.tsx b/apps/sim/app/(landing)/components/site-structured-data/site-structured-data.tsx index a4b8fa24914..591920d098e 100644 --- a/apps/sim/app/(landing)/components/site-structured-data/site-structured-data.tsx +++ b/apps/sim/app/(landing)/components/site-structured-data/site-structured-data.tsx @@ -36,7 +36,7 @@ const SITE_JSON_LD = { sameAs: [ 'https://x.com/simdotai', 'https://github.com/simstudioai/sim', - 'https://www.linkedin.com/company/simstudioai/', + 'https://www.linkedin.com/company/simdotai/', 'https://join.slack.com/t/sim-ott9864/shared_invite/zt-43lp8tc5v-0qrrqHGBKUsvQlpoouH~TA', ], contactPoint: [ diff --git a/apps/sim/app/(landing)/files/components/feature-graphics/interactive-library-folder.tsx b/apps/sim/app/(landing)/files/components/feature-graphics/interactive-library-folder.tsx index ad1d78fd5b5..cff20bc4ebd 100644 --- a/apps/sim/app/(landing)/files/components/feature-graphics/interactive-library-folder.tsx +++ b/apps/sim/app/(landing)/files/components/feature-graphics/interactive-library-folder.tsx @@ -84,7 +84,7 @@ export function InteractiveLibraryFolder({ + + + {!workflowRunning && {label}} diff --git a/apps/sim/app/(landing)/cookie-policy/cookie-policy-content.tsx b/apps/sim/app/(landing)/cookie-policy/cookie-policy-content.tsx index 4da00f38e27..563b7b7f9f3 100644 --- a/apps/sim/app/(landing)/cookie-policy/cookie-policy-content.tsx +++ b/apps/sim/app/(landing)/cookie-policy/cookie-policy-content.tsx @@ -44,7 +44,7 @@ export const COOKIE_POLICY_CONFIG: LegalPageConfig = { title: 'Cookie Policy', description: 'What cookies Sim sets, why, how long they last, and how to change your choice at any time.', - lastUpdated: 'September 3, 2026', + lastUpdated: 'September 17, 2026', intro: [ { kind: 'paragraph', @@ -165,7 +165,7 @@ export const COOKIE_POLICY_CONFIG: LegalPageConfig = { [ '__cf_bm', 'Cloudflare', - 'Bot-management check on requests to providers we load, such as HubSpot and X.', + 'Bot-management check on requests to providers we load, such as X.', '30 minutes', ], ]), @@ -177,10 +177,6 @@ export const COOKIE_POLICY_CONFIG: LegalPageConfig = { 'Holds the session state for a specific Analytics property.', '13 months', ], - ['__hstc', 'HubSpot', 'Tracks visits across sessions for the main tracker.', '6 months'], - ['hubspotutk', 'HubSpot', 'Identifies a visitor across form submissions.', '6 months'], - ['__hssc', 'HubSpot', 'Tracks the current session.', '30 minutes'], - ['__hssrc', 'HubSpot', 'Detects whether the visitor restarted their browser.', 'Session'], [ 'ph_*_posthog', 'PostHog', @@ -269,9 +265,8 @@ export const COOKIE_POLICY_CONFIG: LegalPageConfig = { Google Analytics , Google Ads,{' '} - X (Twitter),{' '} - HubSpot, and{' '} - PostHog. + X (Twitter), + and PostHog. ), }, @@ -292,7 +287,6 @@ export const COOKIE_POLICY_CONFIG: LegalPageConfig = { The providers currently in use are{' '} Google{' '} (Analytics and Ads),{' '} - HubSpot,{' '} X (Twitter),{' '} Ahrefs,{' '} PostHog, and{' '} diff --git a/apps/sim/app/(landing)/hubspot-page-view-tracker.test.tsx b/apps/sim/app/(landing)/hubspot-page-view-tracker.test.tsx deleted file mode 100644 index ad0aa6afe8d..00000000000 --- a/apps/sim/app/(landing)/hubspot-page-view-tracker.test.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @vitest-environment jsdom - */ -import { act, StrictMode } from 'react' -import { createRoot, type Root } from 'react-dom/client' -import { afterEach, describe, expect, it, vi } from 'vitest' - -const { navigation } = vi.hoisted(() => ({ navigation: { pathname: '/pricing' } })) - -vi.mock('next/navigation', () => ({ usePathname: () => navigation.pathname })) - -import { HubspotPageViewTracker } from '@/app/(landing)/hubspot-page-view-tracker' - -let root: Root | null = null - -afterEach(() => { - act(() => root?.unmount()) - root = null - navigation.pathname = '/pricing' - window._hsq = [] -}) - -describe('HubspotPageViewTracker', () => { - it('tracks later paths once without query data under Strict Mode', () => { - ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true - const container = document.createElement('div') - root = createRoot(container) - window._hsq = [] - - act(() => - root?.render( - - - - ) - ) - expect(window._hsq).toEqual([]) - - navigation.pathname = '/demo' - act(() => - root?.render( - - - - ) - ) - - expect(window._hsq).toEqual([['setPath', '/demo'], ['trackPageView']]) - }) -}) diff --git a/apps/sim/app/(landing)/hubspot-page-view-tracker.tsx b/apps/sim/app/(landing)/hubspot-page-view-tracker.tsx deleted file mode 100644 index 85c316cea0f..00000000000 --- a/apps/sim/app/(landing)/hubspot-page-view-tracker.tsx +++ /dev/null @@ -1,31 +0,0 @@ -'use client' - -import { useEffect, useRef } from 'react' -import { usePathname } from 'next/navigation' - -let hasTrackedInitialPageView = false - -/** - * The consent-gated HubSpot loader auto-tracks its first page. Pushes a manual - * pageview through `_hsq` for later client navigations. - */ -export function HubspotPageViewTracker() { - const pathname = usePathname() - const lastTrackedPathRef = useRef(null) - - useEffect(() => { - if (lastTrackedPathRef.current === pathname) return - lastTrackedPathRef.current = pathname - - if (!hasTrackedInitialPageView) { - hasTrackedInitialPageView = true - return - } - - window._hsq = window._hsq || [] - window._hsq.push(['setPath', pathname]) - window._hsq.push(['trackPageView']) - }, [pathname]) - - return null -} diff --git a/apps/sim/app/(landing)/landing-consent-tracking.tsx b/apps/sim/app/(landing)/landing-consent-tracking.tsx index 08997063938..96a5a3e3f73 100644 --- a/apps/sim/app/(landing)/landing-consent-tracking.tsx +++ b/apps/sim/app/(landing)/landing-consent-tracking.tsx @@ -1,18 +1,11 @@ 'use client' import { useConsentScript } from '@c15t/nextjs/headless' -import { HUBSPOT_SCRIPT, X_PIXEL_SCRIPT } from '@/lib/consent/scripts' -import { HubspotPageViewTracker } from '@/app/(landing)/hubspot-page-view-tracker' +import { X_PIXEL_SCRIPT } from '@/lib/consent/scripts' import { XPageViewTracker } from '@/app/(landing)/x-page-view-tracker' export function LandingConsentTracking() { - const hubspot = useConsentScript({ script: HUBSPOT_SCRIPT, unmountBehavior: 'keep' }) const xPixel = useConsentScript({ script: X_PIXEL_SCRIPT, unmountBehavior: 'keep' }) - return ( - <> - {hubspot.status === 'ready' && } - {xPixel.status === 'ready' && } - - ) + return xPixel.status === 'ready' ? : null } diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx index 1e17cf742fb..71f94b3b37b 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx @@ -481,7 +481,7 @@ export function ResourceTabs({ if (isMultiDrag) { e.dataTransfer.effectAllowed = 'copy' e.dataTransfer.setData(SIM_RESOURCES_DRAG_TYPE, JSON.stringify(selected)) - const dragImage = buildMultiDragImage(e.currentTarget.closest('[role="tablist"]'), selected) + const dragImage = buildMultiDragImage(e.currentTarget.closest('[data-tab-strip]'), selected) if (dragImage) { e.dataTransfer.setDragImage(dragImage, 16, 16) dragImageRef.current = dragImage diff --git a/apps/sim/lib/consent/scripts.test.ts b/apps/sim/lib/consent/scripts.test.ts index 48477954a39..a43c11045f7 100644 --- a/apps/sim/lib/consent/scripts.test.ts +++ b/apps/sim/lib/consent/scripts.test.ts @@ -7,7 +7,6 @@ import { GLOBAL_CONSENT_SCRIPTS, GOOGLE_ADS_ID, GOOGLE_ANALYTICS_ID, - HUBSPOT_SCRIPT, X_PIXEL_SCRIPT, } from '@/lib/consent/scripts' @@ -27,7 +26,6 @@ const CALLBACK_INFO: ConsentScriptCallbackInfo = { afterEach(() => { window.dataLayer = [] window.gtag = undefined - window._hsq = [] window.history.replaceState({}, '', '/') }) @@ -49,8 +47,7 @@ describe('consent scripts', () => { ]) }) - it('keeps landing vendors in separate consent categories', () => { - expect(HUBSPOT_SCRIPT).toMatchObject({ id: 'hubspot', category: 'measurement' }) + it('gates the landing conversion pixel on marketing consent', () => { expect(X_PIXEL_SCRIPT).toMatchObject({ id: 'x-pixel', category: 'marketing', @@ -86,13 +83,4 @@ describe('consent scripts', () => { `https://www.googletagmanager.com/gtag/js?id=${GOOGLE_ADS_ID}` ) }) - - it('gives HubSpot a query-free path before its automatic first page view', () => { - window.history.replaceState({}, '', '/demo?email=private@example.com#booking') - window._hsq = [] - - HUBSPOT_SCRIPT.onBeforeLoad() - - expect(window._hsq).toEqual([['setPath', '/demo']]) - }) }) diff --git a/apps/sim/lib/consent/scripts.ts b/apps/sim/lib/consent/scripts.ts index c431bec29cc..b797f580a56 100644 --- a/apps/sim/lib/consent/scripts.ts +++ b/apps/sim/lib/consent/scripts.ts @@ -23,12 +23,6 @@ export const X_DEMO_BOOKED_EVENT_ID = 'tw-q5xbl-q5xbn' as const const AHREFS_ANALYTICS_KEY = 'WJ9yWTBAiQKZAE/2TyU/yA' as const -declare global { - interface Window { - _hsq?: unknown[][] - } -} - const GOOGLE_ANALYTICS_SCRIPT = gtag({ id: GOOGLE_ANALYTICS_ID, category: 'measurement', @@ -70,15 +64,3 @@ export const GLOBAL_CONSENT_SCRIPTS = [ /** Marketing-page integrations that should not load on a direct workspace visit. */ export const X_PIXEL_SCRIPT = xPixel({ pixelId: X_PIXEL_ID }) - -/** HubSpot has no first-party c15t helper, so it uses the generic script contract. */ -export const HUBSPOT_SCRIPT = { - id: 'hubspot', - src: 'https://js-na2.hs-scripts.com/246720681.js', - category: 'measurement', - async: true, - onBeforeLoad: () => { - window._hsq ||= [] - window._hsq.push(['setPath', window.location.pathname]) - }, -} as const diff --git a/apps/sim/lib/core/security/csp.ts b/apps/sim/lib/core/security/csp.ts index c9a7d3b9e95..cd2664c6bb3 100644 --- a/apps/sim/lib/core/security/csp.ts +++ b/apps/sim/lib/core/security/csp.ts @@ -83,12 +83,6 @@ const STATIC_SCRIPT_SRC = [ 'https://www.googleadservices.com', 'https://googleads.g.doubleclick.net', 'https://analytics.ahrefs.com', - // HubSpot tracking (landing pages) — loader plus the - // analytics/form-tracking/banner scripts it injects as