Move (not copy) static-pages rebrand below-the-fold into packages/turbo-shared/src/btf/, then rewire static-pages only (mobile + desktop homepage variants) to consume it. home-mobile is out of scope for this task.
The refactor must surface and remove implicit environment coupling. BTF sections must not depend on static-pages-only contexts, stores, or global hooks. Side effects and consumer-specific behaviour belong at the composition boundary, injected via props, slots, or small effect components — not inside leaf sections.
Success = static-pages mobile + desktop homepages render BTF identically to today, with BTF living in turbo-shared and ready for home-mobile later without further architectural work.
This PR must not leave duplicates. Every file that moves to turbo-shared must be deleted (or reduced to a thin re-export wrapper) from static-pages. The git diff should read as a relocate, not a fork.
Source (rebrand only — ignore legacy):
apps/static-pages/src/components/pages/home-rebrand/in-app-home-below-the-fold.tsxapps/static-pages/src/components/pages/home-rebrand/promotional-home-below-the-fold.tsxapps/static-pages/src/components/pages/home-rebrand/download-options.tsxapps/static-pages/src/components/rebrand/product-showcase.tsxapps/static-pages/src/pages/[locale]/app/index.tsx(component only — strip page exports)- Walk import graph from those roots; move all required files + SCSS + assets (PNGs, SVGs) that are BTF-only consumers
Do not use: apps/static-pages/src/components/pages/home/ (legacy)
Consumers to rewire (static-pages only):
- Mobile:
home-rebrand/minimal-homepage.tsx→InAppHomeBelowTheFold - Desktop promotional:
home-rebrand/desktop-homepage.tsx→PromotionalHomeBelowTheFold - Desktop minimal: same
InAppHomeBelowTheFoldas mobile
Do not touch: apps/home-mobile, apps/home-desktop, apps/nextssg
We have been burned by duplicate trees before. This PR is a single relocation of BTF ownership to turbo-shared.
| Do | Don't |
|---|---|
git mv (or equivalent) files into packages/turbo-shared/src/btf/ |
Copy files and leave originals in static-pages |
| Delete moved source files from static-pages | Keep parallel implementations |
Leave only thin wrappers in static-pages (~15 lines) that import from @staticpages/turbo-shared/btf |
Leave full component implementations in static-pages |
| Update all import paths to point at turbo-shared | Re-export from old paths without deleting the body |
Git diff expectation: For each BTF component/asset, you should see:
packages/turbo-shared/src/btf/...— added (or renamed from static-pages path)apps/static-pages/src/...— deleted or reduced to a thin injection wrapper
If a file under apps/static-pages is still shared by non-BTF code (e.g. a rebrand component used by /app page AND homepage BTF), do not duplicate it — either:
- Move it to turbo-shared only if BTF is the sole consumer, or
- Leave it in static-pages and import it from there temporarily, with a
// TODO: evaluate shared ownership— but prefer moving when BTF is the only consumer.
Assets & icons: If an asset/SVG is only referenced by the BTF subtree, move it into turbo-shared/src/btf/assets/ (or btf/icons/) and delete the static-pages copy. Update imports within btf/ only. Do not leave two copies on disk.
/static-assets/... URL paths (served from turbo-public) stay as-is — those are not repo files to move.
Three layers inside turbo-shared/src/btf/:
sections/ — presentational; NO @hooks, NO @stores, NO @contexts
body/ — BtfSharedBody: ProductShowcase + AppEmbed; slot-driven
compositions/ — variant wrappers + side-effect bundles
mobile-in-app.tsx → BelowTheFoldInApp
desktop-promotional.tsx → BelowTheFoldPromotional
shell/ — BtfShell: wrapper styles, id, testId, BtfContext provider
Public exports:
export { BelowTheFoldInApp } from "./compositions/mobile-in-app";
export { BelowTheFoldPromotional } from "./compositions/desktop-promotional";
export { BtfSharedBody } from "./body"; // for StorybookAdd turbo-shared package.json export: "./btf": "./src/btf/index.tsx"
BtfShell accepts injected dependencies and provides them via a narrow BtfContext (not a recreation of _app):
type BtfInjectedProps = {
id?: string;
testId?: string;
decorateUrl: (url: string) => string;
firePixel: (id: string, params?: Record<string, unknown>) => void;
LinkComponent: React.ComponentType<{
href: string;
children: React.ReactNode;
onClick?: () => void;
className?: string;
}>;
device: Device | null; // passed from parent useDevice() — sections do NOT call useDevice from static-pages
};Rules:
- Sections call
useBtfContext()fordecorateUrl,firePixel,Link,device— never@hooks,@stores,@contexts,@utils/pixeldirectly - Every
@hooks/@stores/@contextsimport found during port → refactor to context injection OR move tocompositions/*/effects.tsx - If refactor is unclear: leave
// TODO: side-effect — needs injection+console.warn— do not silently keep static-pages coupling
Slots on BtfSharedBody:
slots?: {
prelude?: React.ReactNode; // in-app: DownloadOptions + heading
hero?: React.ReactNode; // in-app: faux browser; promotional: unsupported hero
};
pixels?: { impression?: boolean; scroll?: boolean; bounce?: boolean }; // AppEmbed flagsCompositions pass different slots; shared body stays identical.
Side effects as explicit effect components in compositions only:
// compositions/effects.tsx — hooks live HERE, not in sections
<OriginContentEffect content="details" />
<AtbCampaignEffect page="home" />
<MotionConfig reducedMotion="user" />static-pages passes real implementations into BtfInjectedProps from its existing runtime (origin-store, Pixel class, Link component, useDevice).
Replace moved files with thin wrappers — no BTF logic remains in static-pages:
// home-rebrand/in-app-home-below-the-fold.tsx becomes ~15 lines
import { BelowTheFoldInApp } from "@staticpages/turbo-shared/btf";
import { useDevice } from "@hooks";
import { decorateOriginInUrl } from "@utils/origin";
import { useOriginWithReadyState } from "@hooks/use-origin";
import Link from "@components/link";
import Pixel from "@utils/pixel";
export default function InAppHomeBelowTheFold() {
const device = useDevice();
const originState = useOriginWithReadyState();
if (!device) return null;
return (
<BelowTheFoldInApp
device={device}
decorateUrl={(url) =>
originState.isOriginReady
? decorateOriginInUrl(url, (o) => o.setIfEmpty("content", "details"))
: url
}
firePixel={(id, params) => new Pixel(id, { params }).fire()}
LinkComponent={Link}
/>
);
}Same pattern for promotional-home-below-the-fold.tsx → BelowTheFoldPromotional.
Remove OriginCustomizerProvider from minimal-homepage.tsx / desktop-homepage.tsx if BelowTheFold* compositions own OriginContentEffect.
-
/home/mobileBTF matches pre-refactor (visual +data-testid="homepage-btf",download-options,belowTheFold) - Desktop promotional homepage BTF works (
PromotionalHomeBelowTheFoldpath) - Desktop minimal homepage BTF works (
InAppHomeBelowTheFoldpath) - BTF CTA origins still contain
__detailswhere expected today - No
@hooks/@stores/@contextsimports remain insideturbo-shared/src/btf/sections/orbody/ - No duplicate BTF files — grep static-pages for moved component bodies; only thin wrappers should remain
- Optional: add Storybook story for
BtfSharedBodywith mock injection props (no Chromatic ignore needed for isolated story)
- Mechanical move first; refactor boundaries as you fix broken imports
- Ugly internals in
btf/are fine if sections are clean and injections are explicit // TODO+console.warnacceptable for uncertain side effects — document what env assumption was found- The point of this task is cleanliness: every removed
@hooksimport is a discovered coupling; log them in PR description - Add turbo-shared deps as needed:
@staticpages/design-system,motion,react-intersection-observer, etc.
Done when: static-pages mobile + desktop BTF renders from @staticpages/turbo-shared/btf, moved source files are deleted from static-pages (not duplicated), zero coupling to static-pages internals inside btf/sections or btf/body, and home-mobile could adopt the same exports by passing its own injection props without further structural work.