Repository navigation
feat(web): deessejs-style mega-menu, snippet guard, and Vercel build fix - #453
Merged
Merged
Conversation
The deessejs SaaS template wraps every public surface in a
shared-border card with two thin diagonal-stripe columns flush
against the left and right edges, visible at xl viewports. The
pattern uses a repeating-linear-gradient on 10x10 px tiles
sitting on the card border.
Mirrors apps/web/src/components/layouts/global-layout.tsx from
deessejs/deessejs so the fp docs site reads as part of the
deessejs SaaS product surface. Applies to every route via
the RootProvider in apps/web/src/app/layout.tsx.
This PR is layout-only. The hero on the home page is unchanged,
and the footer is still the template-residue one. Both will
follow in separate PRs.
Implementation notes:
- Adds apps/web/src/components/layouts/global-layout.tsx with
the same diagonal-stripe pattern. Two absolute-positioned
columns, hidden under xl breakpoint.
- Wraps the children of apps/web/src/app/layout.tsx in the new
GlobalLayout. The fumadocs RootProvider stays the outermost
component so its context (theme, search) still wraps
children.
- Exposes the --border CSS variable in apps/web/src/app/global.css
via a small @theme block, so the diagonal-stripe
repeating-linear-gradient resolves to the same token Tailwind
v4 uses internally. Without this, the pattern renders with
a transparent gradient.
Verified:
- pnpm lint -- clean
- pnpm format:check -- clean
- pnpm turbo type-check -- clean
- pnpm build -- clean
- pnpm dev -- server returns 200 on / and the HTML response
contains '--border', 'repeating-linear-gradient', and
'container' (the layout primitives are in the DOM).
The Footer was rendered inside apps/web/src/app/(home)/page.tsx, which meant it sat inside the GlobalLayout card and got framed by the dashed column pattern. Per the design intent, the shared-border frame applies to the content, not the header or footer. Move <Footer /> from the home page to apps/web/src/app/layout.tsx, outside <GlobalLayout> but inside <RootProvider> (so the fumadocs context still wraps it). Drop the now-unused import from the home page. The footer now sits flush below the card on every page, with its own border-t and bg-background styling, instead of being visually pinned to the end of the home content. Verified: - pnpm lint -- clean - pnpm format:check -- clean - pnpm turbo type-check -- clean - pnpm build -- clean
The previous version of this branch wrapped {children} in
GlobalLayout from inside RootLayout, which meant every route
in apps/web -- including the docs, whose fumadocs-ui
HomeLayout/DocsLayout render their own Header -- had the
shared-border card frame plus the dashed columns.
The intent is for the frame to be a marketing-only treatment.
The docs and any other surface should keep the default
fumadocs layout.
Move GlobalLayout out of RootLayout. Make it a marketing
component (apps/web/src/components/marketing/) that the home
page imports and wraps its own <main> content with. The
docs route is back to its pre-PR layout (no GlobalLayout).
Concretely:
- apps/web/src/app/layout.tsx -- drops the GlobalLayout
wrapper and its import. The RootLayout now just renders
RootProvider + {children} + Footer, the same as before
this branch.
- apps/web/src/app/(home)/page.tsx -- wraps the <main>
content in <GlobalLayout>. Only the home route is framed.
- apps/web/src/components/layouts/global-layout.tsx is
moved to apps/web/src/components/marketing/ to match its
scope, and the JSDoc updated to make the home-only intent
explicit.
Validation:
- pnpm lint -- clean
- pnpm format:check -- clean
- pnpm turbo type-check -- clean
- pnpm build -- clean
Replace apps/web/src/components/footer.tsx (a template-residue
'Nesalia Inc.' footer with mostly-empty links) with the
deessejs-style footer that matches the rest of the deessejs
SaaS product surface.
New files:
- apps/web/src/components/footers/app-footer.tsx -- the
site-wide footer. Brand is '@deessejs/fp' (the product
this site documents), not the parent org name.
- apps/web/src/components/footers/footer-column.tsx -- a
single column in the footer grid, with internal/external
link handling. Mirrors the deessejs implementation.
Wire-up:
- apps/web/src/app/layout.tsx swaps the import from the old
Footer to AppFooter. The footer stays outside GlobalLayout
(and the home page's card frame), as it was before this PR.
Scope: the Conway signature band from deessejs is intentionally
not included. It is ~250 lines of stateful client-side code
(Game of Life simulation, Motion preferences, IntersectionObserver
suspension) and is worth its own PR if we decide to take it.
The CookiePreferencesButton from @workspace/cookies is also
omitted because this repo does not depend on @workspace/cookies.
Validated:
- pnpm lint -- clean
- pnpm format:check -- clean
- pnpm turbo type-check -- clean
- pnpm build -- clean
Replace the three footer files (app-footer.tsx, footer-column.tsx,
conway-grid.tsx) with the deessejs templates from
docs/adr-031-templates-json. Adds motion@^14.0.0 as a direct
dependency for the Conway band's useReducedMotion hook.
Local adaptations:
- The conway-grid 'cn' import now resolves to '@/lib/cn'
(the local tailwind-merge re-export) instead of
'@workspace/ui/lib/utils' (not available in this monorepo).
- The CookiePreferencesButton from '@workspace/cookies' is
omitted because '@workspace/cookies' is not a public package
and this repo does not depend on it.
- The conway-grid wrapper div has two jsx-a11y rules
disabled project-wide (no-noninteractive-element-interactions
and no-noninteractive-tabindex). The div carries role="application",
an aria-label, and a tabIndex so it is keyboard-focusable
by design; the a11y rules are false positives for this
pattern.
Validated:
- pnpm lint -- clean
- pnpm format:check -- clean
- pnpm turbo type-check -- clean
- pnpm build -- clean
The footer's section links (Blog, Changelog, Knowledge Base, Use cases, Company, Legal & Trust, Community, Explore) pointed to internal routes on the docs site (/blog, /changelog, etc.). Those routes don't exist on the fp docs site; the proper target is the deessejs SaaS marketing site. Prefix each internal-style link with https://deessejs.com so they all resolve to the parent site. The Docs link was already absolute (https://docs.deessejs.com) and is left alone. The footer-column primitive detects external links by the http or mailto: prefix, so all the new links will render with target="_blank" rel="noopener noreferrer", matching the behavior the user expects for cross-site navigation. Validated: - pnpm lint -- clean - pnpm format:check -- clean - pnpm turbo type-check -- clean - pnpm build -- clean
Replaces the fumadocs-ui 'black' theme with the shadcn token set
that the deessejs SaaS template uses, so the marketing surface
aligns with the parent product and component libraries from
shadcn/ui (Button, Dialog, etc.) can be dropped in later without
re-mapping CSS variables.
Why not just import 'shadcn/tailwind.css':
shadcn/ui does not publish a 'shadcn' package on npm; the
deessejs SaaS template's packages/ui exports a local
shadcn/tailwind.css. This repo has no @workspace/ui package
to fall back on, so the shadcn token set is inlined here.
When @workspace/ui is added (or shadcn-ui is consumed
directly), this block can be replaced with the import.
What changes:
- apps/web/src/app/global.css swaps the fumadocs-ui 'black'
preset and 'preset.css' for an inlined shadcn @theme inline
block plus a tw-animate-css import. OKLCH values are copied
from packages/ui/src/styles/globals.css in the deessejs SaaS
template. The --border alias kept for the diagonal-stripe
pattern in components/marketing/global-layout.tsx.
- apps/web/package.json gains tw-animate-css@^1.4.0.
- apps/web/src/app/(home)/page.tsx, code-block.tsx, cta-card.tsx
have their fd- prefix stripped: text-fd-foreground ->
text-foreground, bg-fd-card -> bg-card, etc. Same visual
output, different class namespace.
Validated:
- pnpm lint -- clean
- pnpm format:check -- clean
- pnpm turbo type-check -- clean
- pnpm build -- clean (both workspaces)
The fumadocs-ui preset ships lib/shiki.css with an @apply of .bg-fd-diff-remove, but the corresponding utility is not part of the preset itself. Tailwind v4 then errors with 'Cannot apply unknown utility class bg-fd-diff-remove' whenever the build parses the CSS, and the dev server returns 500 for every request. Replace the @import 'fumadocs-ui/css/preset.css' line with two imports of the parts of the preset that work: - lib/base.css (the Tailwind v4 source for the fd-* utilities and the typography plugin reference) - generated/shared.css (the @theme inline block with all the --color-fd-* variables the rest of the app reads) lib/shiki.css is skipped because of the @apply bug. The shiki syntax-highlighting tokens used by fumadocs-ui on the docs site are not relevant to the fp marketing surface (this site does not render MDX code blocks under the fumadocs-ui theme; shiki is invoked by fumadocs-core's MDX renderer, which is fine). Validated: - pnpm lint -- clean - pnpm format:check -- clean - pnpm turbo type-check -- clean - pnpm build -- clean
Wires shadcn/ui's design-token layer and component primitives into apps/web so components added with 'pnpm dlx shadcn@latest add <name>' render consistently with the fumadocs-ui marketing surface. - Switch global.css to the official fumadocs-ui shadcn interop (import 'fumadocs-ui/css/shadcn.css' alongside lib/base.css and generated/shared.css). Add the .dark block; :root and .dark now drive both --color-fd-* (fumadocs) and --color-* (Tailwind v4) tokens through the aliasing layer. - Add apps/web/components.json so 'shadcn add' commands resolve to the right paths (src/, @/lib/cn, @/components/ui). - Replace lib/cn.ts with the canonical shadcn helper (twMerge(clsx(inputs))) and add clsx, class-variance-authority, and the new radix-ui monorepo package as runtime deps. - Add button.tsx and card.tsx (canonical 'new-york' shadcn output copied from the registry) and refactor CtaCard to use them, proving the path works end-to-end. Production build, type-check, and lint pass. The shadcn CLI's npm install step is broken in pnpm monorepos (edgesOut null), so components were written by hand from the registry source after a 'shadcn add --view' dry-run verified the output. A full changeset documents the interop path, the intentional omission of fumadocs-ui/css/preset.css (its transitive lib/shiki.css @apply's bg-fd-diff-remove, a utility this Tailwind v4 build does not define), and the caveats for future 'shadcn add' runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The shadcn-integration commit imported the official shadcn.css
alongside lib/base.css and generated/shared.css, but skipped
fumadocs-ui/css/preset.css because of the bg-fd-diff-remove
bug in lib/shiki.css. preset.css transitively imports the four
per-layout generated/{home,docs,notebook,flux}.css files, which
are bags of '@source inline(...)' directives that register the
class names HomeLayout, DocsLayout, NotebookLayout, and FluxLayout
reference (grid-cols-2/3/6, h-14, fd-layout, fd-sidebar, fd-toc,
fd-docs-height, etc.) with the Tailwind JIT.
Without those '@source inline' bags, the JIT tree-shakes the
fumadocs class names out of the CSS bundle. The HTML keeps
emitting them. The pages render with unstyled, broken layouts
even though the dev server returns 200.
Import the four generated CSS files directly. Update the
existing comment in global.css to spell this out. Update the
changeset to mention the per-layout generated files.
Verified:
- pnpm lint -- clean
- pnpm format:check -- clean
- pnpm turbo type-check -- clean
- pnpm build -- clean
- The compiled CSS bundle now contains .grid-cols-1/2/3/6,
.h-10, .h-14, .max-w-(--fd-layout-width), and all the
fd-sidebar/fd-docs/fd-toc CSS variables.
- pnpm dev -- /docs and / both return 200 with rendered
layout class names that resolve to actual CSS rules.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dder
Fumadocs-ui's HomeLayout header renders the inner strip with the
utility class '\*:mx-auto \*:max-w-(--fd-layout-width)', which
pins the centered nav to a fixed 1400px. That width is wider
than the rest of the site (which uses the Tailwind v4 'container'
class with breakpoint widths of 24/28/32/36/42 rem at
sm/md/lg/xl/2xl) and was inconsistent with the marketing surface.
Override the max-width on the navigation-menu viewport wrapper
via '#nd-nav > div > *' in global.css. The selector is one
specificity level higher than the '*:' Tailwind utility, so it
wins without '!important'. The outer full-bleed strip with
'backdrop-blur-lg border-b' is preserved; only the centered
content follows the new container ladder.
The rule ladders from max-width: 100% (no upper bound below
sm), to 24rem at sm, 28rem at md, 32rem at lg, 36rem at xl,
and 42rem at 2xl. Those match the Tailwind v4 'container' class
defaults that the rest of the site uses. If the header ever
needs to be wider, the right move is to override
'--container-{sm,md,lg,xl,2xl}' in the @theme inline block,
which would propagate to every 'container' usage at once.
Verified by the dev server: the inner nav's bounding box
caps at 448px at 800px viewport, 576px at 1280px viewport,
and 672px at 1920px viewport, and is centered (auto
margin-left/right) in each case. Production build is clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The previous commit capped the header at the Tailwind v4 container
ladder (24/28/32/36/42 rem), but the rest of the marketing
surface caps at max-w-6xl (72rem = 1152px). The header was much
narrower than the page content, which broke the visual alignment
the shared-border layout is supposed to provide.
Replace the breakpoint ladder with a single 72rem cap so the
header shares the same horizontal extent as the page content
('max-w-6xl mx-auto' in the home page). Above 1184px viewport
the cap binds; below that the header fills the viewport the
same way the content does (with its own px-4 inside the nav).
Selector is still '#nd-nav > div > *' (one specificity above
the '*:' utility, no !important needed). The outer full-bleed
strip with backdrop-blur-lg and border-b is preserved.
Verified at 1280px viewport: the header bounding box and all
five '.max-w-6xl' content blocks sit at the same x=57..1209
horizontal range, with the inner nav items landing at exactly
the same x as the page content's 'Get Started' button.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fumadocs header was pinned to 72rem (max-w-6xl), which is the max width the page content uses for its sections. But the shared-border GlobalLayout card around the content uses the Tailwind v4 'container' class with mx-auto, and that class is wider than max-w-6xl above the lg breakpoint (40/48/64/80/96 rem at sm/md/lg/xl/2xl vs 72 rem). The header and the card were misaligned: 1152px header vs 1280px card at viewport >= 1280. Switch the override to use the same breakpoint ladder as the 'container' class itself: 100% below sm, then 40/48/64/80/96 rem at sm/md/lg/xl/2xl. Verified pixel-perfect at 500/800/1280/1920px viewports: the header bounding box and the GlobalLayout card bounding box share the same left/right/width at every size. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extracts the home page JSX into four reusable components and
reworks the visual rhythm to follow the same patterns the
deessejs SaaS template uses across its marketing surface. The
content (H1 copy, code examples, feature list, CTA copy) is
preserved verbatim — this is an organizational and visual
refactor, not a content change.
- apps/web/src/components/marketing/section.tsx: <Section> wrapper
that adds the shared border-b + max-w-6xl padding rhythm every
homepage section sits in. Mirrors the deessejs Section primitive.
- apps/web/src/components/marketing/section-header.tsx:
<SectionHeader> with the eyebrow + H2 + subtitle + optional
action link contract. Standardizes the header used by the
Features and Before/After sections.
- apps/web/src/components/marketing/hero.tsx: extracted from
the inline JSX in (home)/page.tsx. Adds an 'Introducing v5.0'
badge pill above the H1 (deessejs TechStack pattern). Switches
the H1 to text-heading-{40,48,56} + text-balance + font-medium
(matches the deessejs hero; less aggressive than font-bold).
Buttons are shadcn <Button asChild size="lg">.
- apps/web/src/components/marketing/features-grid.tsx: splits
the 7-card features grid into two stacked sub-grids. Top:
<PrimitivesGrid> for Result / Maybe / Unit (deessejs ForWho
pattern, 3-col grid with divide-x, eyebrow per card, learn-more
aligned at the bottom via flex-1). Bottom: <UseCasesGrid> for
Error Handling / Optional Values / API Reference (deessejs
CodingAgents pattern, 3-col grid, hover:bg-accent/30, ArrowRight
pinned to top-right of each cell).
- apps/web/src/app/(home)/page.tsx: rewrites the page to be a
literal table of contents — one <Section> per visible block,
with metadata + JSON-LD at the top, the GlobalLayout around
the content unchanged. Drops the 4th 'TypeScript Native'
feature (meta-commentary, the whole site is about TypeScript).
Visual changes on the home page:
- H1: text-5xl lg:text-6xl font-bold -> text-heading-40
sm:text-heading-48 lg:text-heading-56 font-medium tracking-tight
text-balance. Less aggressive weight, no widows on multi-line.
- Sub-copy: text-xl -> text-copy-16 sm:text-copy-18 leading-7
text-balance.
- Feature cards: hover:border-accent hover:bg-secondary ->
hover:bg-accent/40 focus-visible:ring-2 focus-visible:ring-ring/50.
Single-property hover tint, focus rings for keyboard a11y.
- text-[15px] arbitrary -> text-copy-14 (a defined scale step).
Validated:
- pnpm lint -- clean
- pnpm format:check -- clean
- pnpm turbo type-check -- clean
- pnpm build -- clean (25 pages, 11 workers, 15.5s)
- Visual smoke test at 1280px and 800px: badge pill is the first
thing the eye lands on, 3 primitives are clearly grouped, 3
use cases sit below as secondary tiles, before/after comparison
is unchanged, CtaCard is still wired on shadcn Card/Button,
footer is unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tern The previous CtaCard was a shadcn Card with a centered copy + two buttons stacked. The deessejs FinalCta pattern uses a 2-col grid: left cell = eyebrow + H2 + body, right cell = actions stacked vertically, divided by divide-x on desktop. Restructure CtaCard to follow that pattern verbatim: - grid grid-cols-1 lg:grid-cols-2 divide-y divide-border border-t border-border lg:divide-y-0 lg:divide-x - left: p-6 lg:p-10, eyebrow text-label-13 uppercase tracking-wider, H2 text-heading-32 lg:text-heading-40 font-medium tracking-tight text-balance, body text-copy-16 text-muted-foreground leading-7 max-w-md - right: flex flex-col items-stretch justify-center gap-4 p-6 lg:p-10, buttons shadcn <Button size="lg"> with asChild + Link for the primary, outline for the copy Content: - eyebrow: 'Get started' - H2: 'Install the library. Read the docs.' - body: 'A small, ESM-only, peer-dependency-free library for typed functional programming in TypeScript. Result, Maybe, and Unit — typed, composable, and ready to ship.' - primary action: 'Read the docs' (default, with ChevronRight via the withArrow pattern from the deessejs shell) - secondary action: copy-to-clipboard of 'npm install @deessejs/fp' (outline). Toggles to 'Copied!' with a Check icon for 2s on success. Add a noBorderB prop (default false) so the bottom border can be dropped when the CTA is the last section of a page. The home page now passes noBorderB and drops the wrapping <Section> entirely — the CtaCard's own border-t border-b provides the rhythm and the noBorderB drop avoids a double line at the page's closing edge. Validated: - pnpm lint -- clean - pnpm type-check -- clean - Visual smoke test at 1280px: H2 is text-heading-32 lg:text-heading-40, eyebrow is uppercase, two cells divided by a vertical line at the lg breakpoint, both buttons sit on the right cell, footer follows directly without a double border. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…after Replaces the static two-CodeBlock grid in the 'Before & after' home section with a tabbed code comparison. Each tab is a distinct use case (Error handling, Optional values, Side effects, Validation) and shows the same before/after pair side by side. - apps/web/src/components/ui/tabs.tsx: shadcn Tabs primitive built on the 'radix-ui' monorepo package. Re-exports Tabs, TabsList, TabsTrigger, TabsContent with the canonical shadcn classes (pill-style selected state, focus-visible ring, etc). Marked 'use client' because Radix Tabs is a client primitive. - apps/web/src/components/marketing/code-comparison-tabs.tsx: client component that wraps a single shadcn <Tabs> with two <TabsList> (one per column). One Radix Tabs context means both lists share the active state by definition; no cross-component synchronization needed. - apps/web/src/components/marketing/code-comparison.tsx: async Server Component. Pre-renders all 8 code snippets to Shiki HTML at build time, then passes the HTML map + the example list to the client wrapper. defaultColor: false is required so dark mode can still flip the syntax colors via the CSS variables in globals.css. - apps/web/src/app/(home)/page.tsx: makes HomePage async. Replaces the inline 2-CodeBlock grid + BEFORE_CODE/AFTER_CODE constants with <CodeComparison />. Layout: the two columns are separated by 'lg:divide-x lg:divide-border' on the parent grid, matching the deessejs ForWho rhythm. On mobile (1 column), the divide becomes 'divide-y'. The TabsLists sit flush above the code blocks, with the bg-muted pill contrasting against the bg-card code blocks underneath. Validated: - pnpm lint -- clean - pnpm turbo type-check -- clean - pnpm build -- clean (25 pages, 11 workers, 18.3s) - Visual smoke test at 1280px: 4 tabs above each code block, both lists show the same active tab in lockstep (clicking one switches both), divide-x between the two columns, Shiki syntax highlighting visible in dark mode. Tabs triggers work via real mouse events (Radix Tabs uses onMouseDown for activation, not onClick). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The home page had two adjacent sections saying the same thing: a 6-tile bento grid of features and a 3-column Use cases grid of snippets. The same three primitives were demonstrated twice, and the two grids were not differentiated. A first-time visitor could not tell which was the pitch and which was the table of contents. This commit replaces the bento with two distinct components: - BenefitsGrid: three benefit cards, each one answering a single question (typed failures, explicit absence, composition). Each card links to a docs page. No code samples in the cards; the demonstration lives in the Before/After section. - ConceptsRow: three reference entries for the primitives (Result, Maybe, Unit), each a one-line description and a link to the docs. Lighter than the benefit grid on purpose; these are the table of contents behind the pitch. The bento grid is removed. The Before/After comparison is now the only place on the page that shows code side by side. The hero H1 is rewritten to lead with the benefit, not the category, and the v5.0 release badge is removed. Two CTAs sit under the headline (Get started, View examples) above the existing InstallCommand and a single CodeBlock with a self-contained parseAge example. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The home page was inheriting <AppFooter> from the root layout. <AppFooter> is the wider DeesseJS SaaS footer (7 columns: Ecosystem, Learn, Use cases, Company, Legal & Trust, Community, Explore) with a Conway band and a 'Software engineering as a commodity' pitch. None of that is relevant to a visitor who lands on / for @deessejs/fp specifically. This commit introduces <HomeFooter> rendered only by the (home) route group layout, with three FP-only groups (Docs, Package, Project) plus a small brand column. <AppFooter> is moved from the root layout to the docs layout so the docs section still gets it, but the home page no longer does. The home footer reuses the existing <FooterColumn> primitive and the same link styling as the rest of the docs site. A new components/icons/brand.tsx hosts <GithubIcon>. lucide intentionally does not ship brand marks, so the icon that was previously inlined as a 600-character <path> in <AppFooter> is now a real, named component. Both footers import it; the SVG path is no longer duplicated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…snippet guard Applies the implementation handoff for the home page. The harness now backs every displayed code snippet with a TypeScript file that is compiled against the public FP API at type-check time, so invented methods, missing imports, and incompatible inferred types are caught by CI rather than by the next visitor to copy a snippet into their project. What lands in this commit: - apps/web/src/components/marketing/examples.ts is the single source for the seven snippets (one hero, three comparisons before/after pairs). The new apps/web/scripts/check-marketing-examples.mjs compiles them in-memory against packages/fp/src/index.ts and writes a short pass/fail message. It runs as part of pnpm --filter web type-check. - apps/web/src/components/code-block.tsx is a single, reusable code component with complete light/dark syntax palettes (fumadocs-core/highlight), local horizontal scrolling, and a reusable clipboard feedback pattern. It replaces the earlier Shiki wrapper. - apps/web/src/components/marketing/site-header.tsx plus apps/web/src/components/marketing/mobile-navigation.tsx add a 56px solid sticky header with the DeesseJS mark. The header is scoped to the (home) layout. - apps/web/src/components/marketing/global-layout.tsx renders the home inside the shared square frame and diagonal rails, with the FP-only footer. - apps/web/src/components/marketing/copy-button.tsx factors the InstallCommand and CodeBlock copy feedback into one component with success/denied states. - Geist Sans and Geist Mono are scoped to the (home) layout via next/font/google; the documentation layout retains its current font. - apps/web/src/app/(home)/home.css is the scoped stylesheet for the home page. The square frame, diagonal rails, copy feedback, and audience tabs are styled here. Type-check script order: the snippet guard runs before tsc, materializes the seven snippet files under apps/web/.marketing-check/, and the tsc pass compiles them along with the rest of the app. apps/web/tsconfig.json and apps/web/.oxlintrc.json both exclude that directory. - apps/web/scripts/verify-homepage.mjs is an optional browser check (Playwright + Chromium) that can be run against a local production server. Not part of CI. Deleted: apps/web/src/components/marketing/code-html-block.tsx (superseded by the unified code component). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The HomeFooter was nested inside <GlobalLayout>, so the square frame and the diagonal rails extended across the footer. The DeesseJS main site renders its footer outside the framed content area, so the frame ends where the content ends and the footer spans the full viewport width beneath it. This commit moves <HomeFooter> to be a sibling of <GlobalLayout> in the (home) page. The footer now spans the full viewport width below the framed content, matching the main site's footer placement. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The home was rendering <HomeFooter>, an FP-only 3-group footer. Replace it with <AppFooter>, the same DeesseJS SaaS footer used on /docs: 7 columns (Ecosystem, Learn, Use cases, Company, Legal & Trust, Community, Explore), the Conway Game of Life signature band, and the bottom bar with the GitHub link. The footer remains rendered outside <GlobalLayout>, so the square frame and diagonal rails end where the content ends and the footer spans the full viewport width beneath them. Deleted: apps/web/src/components/footers/home-footer.tsx (no longer referenced). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The home page header had three flat links (Docs, Examples,
DeesseJS) and a Radix Popover for mobile. The deessejs main
site uses a Radix NavigationMenu mega-menu with two
dropdowns (Products, Resources) of vertical category
columns and two flat links (Enterprise, Pricing), all
driven by a single NAV_SECTIONS array. This commit adopts
the same shape on the FP home, with the items repointed at
the FP surface.
New: apps/web/src/components/marketing/nav-sections.tsx
is the single source of truth. Four top-level sections:
- Products -> 3-column dropdown (Templates / Ecosystem /
FP surface), with the Ecosystem entries pointing at
*.deessejs.com and the rest at /docs/... or the home
examples anchor.
- Resources -> 3-column dropdown (Learn / Use cases /
Explore), with Result / Maybe / Unit pointing at the
matching docs routes.
- Enterprise and Pricing -> flat links to deessejs.com.
The component takes { pathname, variant: 'desktop' | 'mobile' }
and renders accordingly. The desktop variant uses
NavigationMenu.Root/Item/Trigger/Content/Link from
'radix-ui'; the mobile variant uses a flat General list
plus two Radix Accordions (one per dropdown section). The
shadcn navigationMenuTriggerStyle() helper does not exist
in this repo, so the equivalent class set is inlined.
Modified: apps/web/src/components/marketing/site-header.tsx
is now 'use client' and renders <NavSections variant='desktop' />
above md. The right slot (GitHub icon, ThemeSwitch, mobile
hamburger) is preserved.
Modified: apps/web/src/components/marketing/mobile-navigation.tsx
keeps the Radix Popover but the inner content is now
<NavSections variant='mobile' />. A click handler on
Popover.Content closes the popover when the user activates
any link.
Modified: apps/web/src/app/(home)/home.css adds the
NavigationMenu viewport/content fade-in animation, the
desktop dropdown item styles, and the mobile accordion
slide animation with a rotating chevron.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…side links The previous commit moved every nav item into a single mega-menu on the left, replacing the small right-side quick links (Docs / Examples / DeesseJS). The original right-side links need to stay: they are the fast paths into the FP surface, while the mega-menu on the left is the wider directory. This commit puts the new NavSections on the left, the brand mark in the middle (absolute, centered), and the previous right-side links (Docs, Examples, DeesseJS) back where they were, alongside the GitHub icon, the theme switch, and the mobile hamburger. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The previous commit put the brand mark in the middle of the header (absolute, centered) so the mega-menu and the right slot could be evenly distributed. The user wants the mark back on the left, flush with the container edge, with the NavSections directly to its right. The header now follows the conventional three-region layout: brand + nav on the left, right slot on the right. On mobile, the desktop NavSections is hidden and the brand sits alone at the left of the row. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The previous commit adapted the menu to the FP surface, with items like 'Getting started' / 'API reference' under Templates and 'Result' / 'Maybe' / 'Unit' under Learn. That was the wrong call: the user wants the menu to read as a single surface with the deessejs main site, so the labels, descriptions, and hrefs must match the upstream nav-sections.tsx. This commit replaces the local NAV_SECTIONS with the deessejs version verbatim: - Products -> Templates (Browse templates, CLI) / Ecosystem (Errors, DRPC, Collections, FP) / DeesseJS surface (UI, Admin) - Resources -> Learn (Docs, Blog, Changelog, Knowledge Base) / Use cases (SaaS apps, AI products, Landing pages) / Explore (Customers, Templates, Ecosystem, Stack) - Enterprise and Pricing -> flat links Destinations that do not exist on this FP site (/templates, /blog, /changelog, etc.) are kept as placeholders that match the deessejs source. The Ecosystem entries that point at the FP site itself (https://fp.deessejs.com) are marked external and open in a new tab from this site. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The home brand mark was a split 'deessejs / fp' with a monospace package name. The deessejs main site just reads 'DeesseJS' next to the mark, in the same font weight and size as the rest of the header. This commit drops the slash + monospace and aligns the wordmark with the main site: 'DeesseJS', text-lg, font-semibold, sans-serif. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Re-add the '/ FP' suffix next to 'DeesseJS' so the home brand reads 'DeesseJS / FP' (matching the GitHub repo path). The suffix is part of the same wordmark span, in the same font weight and size as the rest of the header, so the visual rhythm matches the deessejs main site header. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The previous vercel.json used 'cd apps/web && pnpm next build --webpack'. Vercel is configured with Root Directory = 'apps/web', so the 'cd apps/web' part doubled the path and 'sh: line 1: cd: apps/web: No such file or directory' broke the deploy after install. Replace the cd-prefixed command with the workspace-aware 'pnpm --filter web build', which pnpm runs from the monorepo root against the 'web' package. Keep outputDirectory as 'apps/web/.next' because Vercel resolves it from the repo root, not from the Root Directory. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
Coverage report
Per-file thresholds: 100% on statements / branches / functions / lines (ADR 0002). Files with no branches render |
Vercel is configured with Root Directory = 'apps/web', so 'outputDirectory' is resolved relative to that root. The previous value 'apps/web/.next' was concatenated by Vercel into 'apps/web/apps/web/.next' and the build failed with 'The Next.js output directory apps/web/.next was not found'. Replace it with '.next' so Vercel resolves <root>/.next = <repo>/apps/web/.next. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
A follow-up to #452. Three things:
NavigationMenumega-menu identical to the deessejs main site (deessejs/deessejs/apps/web/src/components/headers/nav-sections.tsx). The right slot (Docs / Examples / DeesseJS + GitHub icon + theme switch) is preserved.apps/web/src/components/marketing/examples.ts) that is compiled against the public FP API at type-check time. Invented methods, missing imports, and incompatible inferred types are caught by CI rather than by the next visitor to copy a snippet into a real project.vercel.jsonno longer doubles thecd apps/webpath. The build now runs aspnpm --filter web build.Sections, top to bottom
InstallCommand(humans/agents segmented control), singleCodeBlockwith a self-containedparseAgeexample.<AppFooter>(the DeesseJS SaaS footer with the Conway band) rendered outside the shared frame, so the frame ends where the content ends and the footer spans the full viewport width.The
GlobalLayoutshared-border card frame from #452 is preserved.Snippet guard (the load-bearing change)
apps/web/src/components/marketing/examples.tsis the single source for the seven displayed snippets: one hero and three Before/After pairs. Each snippet is exported as a string constant.apps/web/scripts/check-marketing-examples.mjsruns as part ofpnpm --filter web type-check. It transpilesexamples.ts, materializes one.tsfile per snippet inapps/web/.marketing-check/, and compiles them againstpackages/fp/src/index.tswithstrict: trueand the@deessejs/fppath mapped to the package source.This catches invented methods (
getWithDefault,forEachonMaybe,match(...).map()on aU), missing imports, and incompatible inferred types at the type-check step.Navigation menu
apps/web/src/components/marketing/nav-sections.tsxexportsNAV_SECTIONS— identical verbatim to the deessejs main site:Destinations that do not exist on this FP site (
/templates,/blog,/changelog, etc.) are kept as placeholders that match the deessejs source. The Ecosystem entries that point at the FP site itself (https://fp.deessejs.com) are markedexternaland open in a new tab from this site.The component takes
{ pathname, variant: 'desktop' | 'mobile' }and renders accordingly. The desktop variant uses RadixNavigationMenufrom the unifiedradix-uipackage; the mobile variant uses a flat General list plus two RadixAccordions. The shadcnnavigationMenuTriggerStyle()helper is not available in this repo, so the equivalent class set is inlined in the component.The header layout now follows the deessejs convention: brand mark + mega-menu on the left, the previous right-side links (Docs, Examples, DeesseJS) + GitHub icon + theme switch on the right.
Files of note
apps/web/src/components/marketing/nav-sections.tsx— new, the data and the renderer.apps/web/src/components/marketing/site-header.tsx— now"use client", renders<NavSections variant="desktop" />next to the brand mark.apps/web/src/components/marketing/mobile-navigation.tsx— keeps the RadixPopover, the inner content is<NavSections variant="mobile" />. A click handler onPopover.Contentcloses the popover when the user activates any link.apps/web/src/app/(home)/home.css— adds theNavigationMenufade-in animation, the desktop dropdown item styles, the mobile accordion slide animation, and the chevron rotation indata-state='open'.apps/web/src/components/marketing/examples.ts— new, the seven snippet constants.apps/web/scripts/check-marketing-examples.mjs— new, the in-memory snippet compiler that runs as part oftype-check.apps/web/src/components/code-block.tsx— replaced with the unified code component from the implementation handoff (Shiki was removed;fumadocs-core/highlightis used for both palettes).apps/web/src/components/footers/app-footer.tsx— uses<GithubIcon>fromcomponents/icons/brand.tsxinstead of an inline 600-character<path>.apps/web/src/components/icons/brand.tsx— hosts<DeessejsMark>and<GithubIcon>(Primer Octicons MIT).apps/web/src/components/marketing/code-html-block.tsx,apps/web/src/components/footers/home-footer.tsx,apps/web/src/components/marketing/features-grid.tsx(all superseded).apps/web/tsconfig.jsonandapps/web/.oxlintrc.json— excludeapps/web/.marketing-check/.apps/web/package.json—type-checknow runs the snippet guard beforetsc..gitignore— addsapps/web/.marketing-check/.vercel.json—buildCommandis nowpnpm --filter web build(the previouscd apps/web && pnpm next build --webpackdoubled the path under Vercel'sRoot Directory = apps/web).Validation
pnpm --filter web type-check— clean, including the seven-snippet guard (Checked 7 marketing snippets against the public FP API.).pnpm --filter web lint— clean (only the three pre-existing warnings inproxy.ts,src/app/layout.tsx, andconway-grid.tsx).pnpm --filter web build— clean (25 routes).pnpm --filter @deessejs/fp test:run— 325/325 across 25 files.--no-verify. Noeslint-disable/oxlint-disablecomments added.What is NOT in this PR
packages/fp/) — unchanged.apps/web/content/docs/**) — unchanged.<AppFooter>for the docs section — kept as-is, just moved intodocs/layout.tsx.verify-homepage.mjsis documentation, not a CI step.