Add TanStack Start marketing app - #811
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (24)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (15)
📝 WalkthroughWalkthroughAdds a new TanStack Start app ( ChangesTanStack marketing app migration to www-start
Review notes (direct, categorized)[blocker] Microfrontends routing coverage — apps/app/microfrontends.json and dev proxy script changes ( [warning] Env URL resolution and test-mode skips — [warning] Sitemap/robots/llms aggregation logic — [nit] Large removals of pitch-deck internals in apps/www (many ranges under apps/www removals). Why: big deletions can leave stale imports or types — search repo for references to removed exports (e.g., PitchDeck, exportSlidesToPdf) and remove or adapt any remaining imports. [nit] Tests: run Vitest with node env and app-specific config ( Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
✨ Finishing Touches📝 Generate docstrings
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e74ca7dfd8
ℹ️ 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".
| "/src/:path*", | ||
| "/@id/:path*", | ||
| "/@vite/:path*", | ||
| "/@react-refresh", | ||
| "/@tanstack-start/:path*", | ||
| "/node_modules/:path*" |
There was a problem hiding this comment.
Add the Vite /@fs route for workspace imports
When the www-start dev server is reached through the aggregate microfrontend proxy, Vite can serve linked workspace packages such as the many @repo/ui imports in this app via /@fs/...; this new Vite route allowlist includes /src, /@id, /@vite, and /node_modules but not /@fs, so those module requests will fall through to the default app and 404 in the canonical local aggregate URL.
Useful? React with 👍 / 👎.
| priority: 0.7, | ||
| }, | ||
| { | ||
| url: `${SITE_URL}/docs/get-started/config`, |
There was a problem hiding this comment.
Remove the nonexistent docs config URL
This sitemap entry advertises /docs/get-started/config, but the migrated docs content only contains get-started/overview and the microfrontends config only preserves legacy /docs/get-started/quickstart; I also found no config.mdx in either docs content tree. In production the sitemap will point crawlers at a URL that is not served by www-start or the legacy www app.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 14
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/www-start/src/app/(app)/_components/blog-category-nav.tsx (1)
20-21:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win[warning] Encode category slugs in URLs and decode for active matching.
Raw slug interpolation can break links/active state for non-safe characters.
Suggested fix
- const currentCategory = /\/blog\/topic\/([^/]+)/.exec(pathname)?.[1]; + const currentCategoryMatch = /\/blog\/topic\/([^/]+)/.exec(pathname)?.[1]; + const currentCategory = currentCategoryMatch + ? decodeURIComponent(currentCategoryMatch) + : undefined; @@ - <NavLink href={`/blog/topic/${category.slug}`}>{category.title}</NavLink> + <NavLink href={`/blog/topic/${encodeURIComponent(category.slug)}`}> + {category.title} + </NavLink>Also applies to: 48-48
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/app/`(app)/_components/blog-category-nav.tsx around lines 20 - 21, The code is extracting the active category slug directly from pathname (currentCategory) and building links with raw slugs, which breaks for unsafe characters; update the extraction to decode the matched slug (use decodeURIComponent on the regex capture for currentCategory) and ensure all generated category URLs use encodeURIComponent(category.slug) when interpolating into hrefs (the link-generation code where categories are mapped, referenced around the usage at line ~48). Keep isHomePage logic unchanged but use the decoded currentCategory for active-state comparisons so encoded URLs and active matching are consistent.
🟡 Minor comments (15)
apps/www-start/src/lib/content-assets.ts-3-7 (1)
3-7:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winReplace manual URL string concatenation with
new URL()inresolveContentAssetSrc(DEV boundary)
apps/www-start/src/lib/content-assets.tsbuilds the DEV asset URL via a customjoinUrl()(${base}${pathname}) even thoughclientEnv.VITE_WWW_START_URLis already a validated URL; the repo guideline for**/*.{ts,tsx}wantsnew URL(...)-based boundary URL handling instead of brittle string joining.Proposed fix
import { clientEnv } from "~/env/client"; - -function joinUrl(baseUrl: string, path: string) { - const base = baseUrl.replace(/\/$/, ""); - const pathname = path.startsWith("/") ? path : `/${path}`; - return `${base}${pathname}`; -} export function resolveContentAssetSrc(src: string | undefined) { if (!src || !src.startsWith("/images/")) { return src; } if (import.meta.env.DEV) { - return joinUrl(clientEnv.VITE_WWW_START_URL, src); + return new URL(src, clientEnv.VITE_WWW_START_URL).toString(); } return src; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/lib/content-assets.ts` around lines 3 - 7, The custom joinUrl function and string concatenation used to build DEV asset URLs (used by resolveContentAssetSrc) is brittle; replace manual joining with the URL API by constructing new URL(path, clientEnv.VITE_WWW_START_URL) (or new URL(pathname, baseUrl) where baseUrl is the validated clientEnv.VITE_WWW_START_URL) so path resolution handles slashes and edge cases correctly; update resolveContentAssetSrc to call new URL(...) instead of joinUrl (and remove or limit joinUrl usage) while keeping behavior identical for absolute paths.apps/www-start/env.build.ts-5-6 (1)
5-6:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win[warning] Validate
PORTbefore numeric conversion.Line 5 can produce
NaN, which then leaks intodevServer.port(Line 42) and can break dev server config consumers.Proposed fix
-const port = process.env.PORT ? Number(process.env.PORT) : undefined; +const rawPort = process.env.PORT; +const parsedPort = rawPort === undefined ? undefined : Number.parseInt(rawPort, 10); +const port = + parsedPort !== undefined && + Number.isInteger(parsedPort) && + parsedPort > 0 && + parsedPort <= 65535 + ? parsedPort + : undefined;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/env.build.ts` around lines 5 - 6, The current assignment to the port variable uses Number(process.env.PORT) directly which can yield NaN and leak into devServer.port; change the logic around the port constant (the port variable that reads process.env.PORT) to validate and convert safely: check that process.env.PORT is present and represents a finite integer (e.g., parse and test with Number.isFinite/Number.isInteger or a regex) before converting, otherwise set port to undefined or a safe default; update any use of port (e.g., devServer.port) to rely on this validated value.apps/www-start/src/app/(app)/(marketing)/(landing)/_components/isometric-hero.tsx-78-78 (1)
78-78:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFix stray
bg-Tailwind token (ignored styling) in isometric hero
apps/www/src/app/(app)/(marketing)/(landing)/_components/isometric-hero.tsxandapps/www-start/src/app/(app)/(marketing)/(landing)/_components/isometric-hero.tsxline 78 both includeclassName="bg- aspect-video ...";bg-isn’t a valid Tailwindbg-*class, so the background styling won’t be applied.- <div className="bg- aspect-video w-full overflow-hidden rounded-md"> + <div className="aspect-video w-full overflow-hidden rounded-md">🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/app/`(app)/(marketing)/(landing)/_components/isometric-hero.tsx at line 78, In the IsometricHero component's JSX (the <div> with className="bg- aspect-video w-full overflow-hidden rounded-md") remove the invalid Tailwind token `bg-` or replace it with the intended background class (e.g., `bg-white`, `bg-transparent`, or the correct `bg-{color}`) so the background styling is applied; update the className on the div in isometric-hero (the element currently using `className="bg- aspect-video w-full overflow-hidden rounded-md"`) to a valid Tailwind background utility.apps/www-start/src/app/(app)/(company)/_components/company-navbar.tsx-13-15 (1)
13-15:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win[warning] Add accessible names to icon-only home links.
Both icon-only
NavLinks are unlabeled for assistive tech (Line 13 and Line 33). Addaria-label(or visually hidden text) so these links are navigable by screen readers.Suggested fix
- <NavLink className="mr-auto flex items-center pr-4" href="/" prefetch> + <NavLink + aria-label="Home" + className="mr-auto flex items-center pr-4" + href="/" + prefetch + > <Icons.logoShort className="h-4 w-4 text-foreground/60 transition-colors hover:text-foreground" /> </NavLink> @@ - <NavLink + <NavLink + aria-label="Home" className="flex items-center transition-opacity hover:opacity-80 md:hidden" href="/" prefetch >Also applies to: 33-39
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/app/`(app)/(company)/_components/company-navbar.tsx around lines 13 - 15, The icon-only home NavLink using Icons.logoShort (the NavLink with className "mr-auto flex items-center pr-4" and href="/") is missing an accessible name; update this NavLink and the other icon-only NavLink later in the file (the second NavLink around lines 33-39) to include an accessible label by adding an aria-label (e.g., aria-label="Home") or by including visually hidden text inside the link so screen readers can identify the destination; ensure the label text is concise and descriptive and that existing styling/layout (flex, spacing) is preserved.apps/www-start/src/app/(app)/(company)/careers/page.tsx-26-29 (1)
26-29:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win[warning] Apply ultracite formatting fixes to unblock CI.
Formatter failures are currently open on Line 26 and Line 114 in this file.
Proposed fix
- const bytes = Uint8Array.from(binary, (character) => - character.charCodeAt(0) - ); + const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0)); @@ -export default function CareersPage({ - content, -}: CareersPageProps = {}) { +export default function CareersPage({ content }: CareersPageProps = {}) {Also applies to: 114-117
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/app/`(app)/(company)/careers/page.tsx around lines 26 - 29, The formatter is failing due to arrow-function parenthesis/line-break style in the Uint8Array.from calls; update the bytes initialization (the const bytes = Uint8Array.from(...) expression) and any other similar Uint8Array.from usages to a single-line, properly formatted arrow callback (e.g., Uint8Array.from(binary, character => character.charCodeAt(0))) or use a named callback function to avoid the multi-line arrow, then run the formatter to ensure both occurrences (the bytes const and the other Uint8Array.from at the later block) are consistently formatted.apps/www-start/src/app/(app)/(marketing)/(content)/pricing/pricing-smoke.test.ts-48-48 (1)
48-48:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win[warning] Fix JSON-LD count assertion logic.
Line 48 currently checks split length, which is off-by-one for occurrence counting and can pass/fail incorrectly.
Proposed fix
- expect(html.split('type="application/ld+json"')).toHaveLength(3); + expect((html.match(/type="application\/ld\+json"/g) ?? []).length).toBe(3);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/app/`(app)/(marketing)/(content)/pricing/pricing-smoke.test.ts at line 48, The test's assertion uses html.split('type="application/ld+json"') which yields N+1 elements for N occurrences (off-by-one); update the assertion to count occurrences correctly by using a regex match on the html string, e.g. replace expect(html.split('type="application/ld+json"')).toHaveLength(3) with something that checks (html.match(/type="application\/ld\+json"/g) || []).length toEqual(3) (or, if you prefer split, assert (html.split('type="application/ld+json"').length - 1) toBe(3)), referencing the html variable in pricing-smoke.test.ts.apps/www-start/src/app/(app)/(company)/company/_components/manifesto-shader.tsx-133-135 (1)
133-135:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win[warning] Add
touchcancelhandling to avoid sticky pressed state on mobile.Line 133 binds press state on touch, but canceled gestures never reset it. That can leave amplitude/speed in the “pressed” target until another interaction.
Diff
wrapper.addEventListener("touchstart", onDown, { passive: true }); wrapper.addEventListener("touchend", onUp); + wrapper.addEventListener("touchcancel", onLeave); return () => { @@ wrapper.removeEventListener("touchstart", onDown); wrapper.removeEventListener("touchend", onUp); + wrapper.removeEventListener("touchcancel", onLeave); };Also applies to: 144-145
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/app/`(app)/(company)/company/_components/manifesto-shader.tsx around lines 133 - 135, The touch handlers addEventListener calls set pressed state on wrapper via onDown and onUp but don't handle the touchcancel event, which can leave the component stuck in a "pressed" state; add a touchcancel listener on the same element that calls the same reset handler as onUp (or a dedicated onCancel that clears pressed/velocity/amplitude) and remove the listener in cleanup just like the others—update the wrapper.addEventListener lines that use onDown/onUp to also register wrapper.addEventListener("touchcancel", onUp) (and the corresponding removal) so canceled gestures reset state; apply the same change for the second occurrence around the existing handlers (the lines that currently call wrapper.addEventListener("touchstart", onDown) and wrapper.addEventListener("touchend", onUp)).apps/www-start/src/app/(app)/(marketing)/(content)/use-cases/use-cases-smoke.test.ts-14-19 (1)
14-19:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win[warning] Clean up
stubEnvafter each test to prevent cross-suite env leakage.Stubbing env in
beforeEachwithoutvi.unstubAllEnvs()can create order-dependent failures when suites share a worker.As per coding guidelines `**/*.test.ts`: “No flaky patterns (timeouts, race conditions)”.Diff
-import { beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; @@ describe("use-case pages smoke", () => { beforeEach(() => { vi.resetModules(); for (const [key, value] of Object.entries(env)) { vi.stubEnv(key, value); } }); + + afterEach(() => { + vi.unstubAllEnvs(); + });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/app/`(app)/(marketing)/(content)/use-cases/use-cases-smoke.test.ts around lines 14 - 19, The test setup stubs environment variables in beforeEach using vi.stubEnv but does not clean them up, which can leak state across suites; add an afterEach that calls vi.unstubAllEnvs() (and optionally vi.resetModules() if needed) to undo stubs and ensure isolation—update the test file to keep the existing beforeEach (with vi.resetModules and vi.stubEnv loop) and add afterEach(() => { vi.unstubAllEnvs(); }) so vi.stubEnv is cleaned up after each test.apps/www-start/src/config/nav.ts-31-35 (1)
31-35:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win[warning]
SOCIAL_NAVDiscord link is a dead anchor inapps/www-start/src/config/nav.ts
href: "#discord"won’t open Discord; users get no destination from this social item.Suggested fix
{ title: "Discord", label: "Discord", - href: "`#discord`", + href: "https://discord.gg/YqPDfcar2C", icon: "discord", external: true, },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/config/nav.ts` around lines 31 - 35, The Discord social item in SOCIAL_NAV uses a dead anchor href "`#discord`"; update the object (the item with title "Discord" / label "Discord") to point to the actual Discord invite or server URL (e.g., "https://discord.gg/your-invite" or your org's Discord URL) instead of "`#discord`", keep external: true, and ensure the href is an absolute URL so the link navigates users to Discord.apps/www-start/src/app/(app)/(marketing)/(content)/use-cases/technical-founders/page.tsx-21-21 (1)
21-21:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win[warning]
whitespace-nowrapcan clip hero copy on small screens inapps/www-start/src/app/(app)/(marketing)/(content)/use-cases/technical-founders/page.tsxThe parent uses
overflow-x-clip; forced no-wrap risks truncated messaging on narrow viewports.Suggested fix
- <p className="whitespace-nowrap text-base text-muted-foreground md:whitespace-normal lg:whitespace-nowrap"> + <p className="text-balance text-base text-muted-foreground"> Connect engineering decisions to business outcomes. Track ROI, revenue impact, and strategic metrics. </p>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/app/`(app)/(marketing)/(content)/use-cases/technical-founders/page.tsx at line 21, The hero paragraph currently forces no-wrap at the base breakpoint causing clipping inside an overflow-x-clip parent; edit the <p> element in apps/www-start/src/app/(app)/(marketing)/(content)/use-cases/technical-founders/page.tsx to remove the base "whitespace-nowrap" (keep responsive rules if needed, e.g., use "whitespace-normal md:whitespace-nowrap" or simply "whitespace-normal lg:whitespace-nowrap") so short screens can wrap text and avoid truncated hero copy.apps/www-start/src/lib/blog-content.test.ts-17-17 (1)
17-17:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win[warning] Replace fixed-count assertion with slug-presence behavior.
Line 17 ties the test to current content inventory, so adding another post breaks it without a real regression.
Proposed fix
- expect(pages).toHaveLength(1); + expect( + pages.some((entry) => entry.slugs[0] === "2026-03-26-why-we-built-lightfast") + ).toBe(true);As per coding guidelines, "
**/*.test.ts: Prefer behavior-based assertions over implementation details".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/lib/blog-content.test.ts` at line 17, The test in blog-content.test.ts currently asserts a fixed page count with expect(pages).toHaveLength(1), which couples the test to current content; replace that with a behavior-based assertion that a specific known post slug exists (e.g., assert pages contains an entry whose slug equals the expected slug), by checking pages (the pages array) for the presence of the expected slug (use pages.some(...) or map(...).includes(...)) so new posts won’t break the test while still validating the required blog item is present.apps/www-start/src/content/legal/terms.mdx-127-127 (1)
127-127:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win[warning] Privacy Policy link path is inconsistent and likely broken
These links point to
/legal/privacy-policy, but the companion privacy document uses/legal/privacy. Align them to one canonical route.Proposed fix
-We use third-party service providers to operate the Service. A list of our current sub-processors is available in our [Privacy Policy](/legal/privacy-policy). +We use third-party service providers to operate the Service. A list of our current sub-processors is available in our [Privacy Policy](/legal/privacy). ... -These Terms, together with our [Privacy Policy](/legal/privacy-policy), constitute the entire agreement between you and Lightfast with respect to the Service. +These Terms, together with our [Privacy Policy](/legal/privacy), constitute the entire agreement between you and Lightfast with respect to the Service.Also applies to: 220-220
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/content/legal/terms.mdx` at line 127, Update the inconsistent privacy link by replacing occurrences of "/legal/privacy-policy" with the canonical route "/legal/privacy" in the terms content (the line containing "A list of our current sub-processors is available in our [Privacy Policy](/legal/privacy-policy)"). Ensure any other matching occurrences (noted also at the other mentioned location) are updated to use "/legal/privacy" so both documents point to the same canonical privacy route.apps/www-start/src/content/legal/privacy.mdx-47-47 (1)
47-47:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win[nit] Keep spelling variant consistent in legal text
Use one variant throughout this document (
organisationis used elsewhere). Line 47 currently usesorganization.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/content/legal/privacy.mdx` at line 47, The sentence containing "organization settings" uses the American spelling while the rest of the legal text uses "organisation"; update that phrase to "organisation settings" in the privacy text (the sentence that starts "When you use Lightfast, we collect and process information..."), and scan the document for any other occurrences of "organization" to replace with "organisation" to keep spelling consistent throughout.apps/www-start/src/app/(app)/_components/app-mobile-nav.tsx-125-133 (1)
125-133:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winSOCIAL_NAV icon type safety isn’t enforced; current rendering silently drops any icon besides twitter/gitHub/discord.
NavItem.iconis typed askeyof typeof Icons, andIconsdefines many keys (not justtwitter | gitHub | discord). (packages/ui/src/types/nav.ts,packages/ui/src/components/icons.tsx)defineNavItems()acceptsLooseNavItemwith[key: string]: unknownand then casts toNavItem[], so invalid/extraiconstrings aren’t rejected at compile time. (apps/www-start/src/types/nav.ts)app-mobile-nav.tsxonly renders icons forgitHub, anddiscord; any other validitem.iconvalue (or any invalid runtime value) produces no icon with no fallback. (apps/www-start/src/app/(app)/_components/app-mobile-nav.tsx)- Fix: render via
const Icon = item.icon ? Icons[item.icon] : undefined(and handle missing icon), and/or tightendefineNavItems()to preserve/validateNavItem’sicontype instead of casting fromunknown.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/app/`(app)/_components/app-mobile-nav.tsx around lines 125 - 133, The mobile nav drops icons because app-mobile-nav.tsx only checks for "twitter"/"gitHub"/"discord" strings instead of using the Icons map or validating NavItem.icon; change rendering to lookup the component from Icons (e.g., const Icon = item.icon ? Icons[item.icon] : undefined) and render <Icon /> when present with a sensible fallback, and also tighten defineNavItems() to preserve NavItem.icon typing (avoid casting LooseNavItem to NavItem[] unchecked) so invalid icon keys are caught at compile time; update references in app-mobile-nav.tsx, NavItem.icon type, and defineNavItems() accordingly.apps/www-start/src/lib/changelog-content.ts-165-204 (1)
165-204:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win[warning] Keep JSON-LD URL fields aligned with canonical URL
Line 111 uses
page.data.canonicalUrl ?? page.urlfor<link rel="canonical">, but Line 173-Line 174 and downstream JSON-LD references still usepage.url. If canonical differs, crawlers get conflicting URL signals.Suggested fix
export function buildChangelogEntryJsonLd(page: ChangelogPage): JsonLdGraph { + const canonicalUrl = page.data.canonicalUrl ?? page.url; + return { "`@context`": "https://schema.org", "`@graph`": [ buildOrganizationEntity(), buildWebSiteEntity(), { "`@type`": "BlogPosting", - "`@id`": `${page.url}`#article``, - url: page.url, + "`@id`": `${canonicalUrl}`#article``, + url: canonicalUrl, headline: page.data.title, description: page.data.description, abstract: page.data.tldr, @@ }, ...(page.data.faq.length > 0 - ? [buildFaqEntity(page.data.faq, page.url)] + ? [buildFaqEntity(page.data.faq, canonicalUrl)] : []), buildBreadcrumbList([ { name: "Home", url: SITE_URL }, { name: "Changelog", url: `${SITE_URL}/changelog` }, - { name: page.data.title, url: page.url }, + { name: page.data.title, url: canonicalUrl }, ]), ], }; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/lib/changelog-content.ts` around lines 165 - 204, The JSON-LD in buildChangelogEntryJsonLd is still using page.url while the page canonical is computed elsewhere; change the function to compute a single canonical variable (e.g. const canonical = page.data.canonicalUrl ?? page.url) and replace all uses of page.url in the JSON-LD block (the BlogPosting "`@id`" and url, the isPartOf "`@id`" references that include SITE_URL, the call to buildFaqEntity(page.data.faq, page.url), and the breadcrumb item for the current page) with that canonical variable so the structured data and breadcrumb use the same canonical URL as the <link rel="canonical">.
🧹 Nitpick comments (12)
apps/www-start/src/lib/blog-content.ts (1)
189-191: ⚡ Quick win[nit] Return a defensive copy from
getBlogPages().Exposing the internal
blogPagesreference makes shared in-memory state mutable by callers.Proposed fix
export function getBlogPages(): BlogPage[] { - return blogPages; + return [...blogPages]; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/lib/blog-content.ts` around lines 189 - 191, getBlogPages currently returns the internal blogPages array by reference, allowing external mutation of shared state; change getBlogPages to return a defensive copy (e.g., using Array.prototype.slice or spread) so callers get a shallow copy instead of the original blogPages array, updating the implementation in the getBlogPages function to return that copy while leaving the blogPages declaration untouched.apps/www-start/src/app/(app)/(marketing)/(landing)/landing-smoke.test.ts (1)
39-39: ⚡ Quick win[nit] Prefer user-facing nav behavior checks over internal slot attributes.
Line 39 tests a component-internal
data-slotcontract, which is fragile and not user-visible behavior.As per coding guidelines:
**/*.test.ts: "Prefer behavior-based assertions over implementation details".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/app/`(app)/(marketing)/(landing)/landing-smoke.test.ts at line 39, The test in landing-smoke.test.ts currently asserts an implementation detail via expect(html).toContain('data-slot="navigation-menu-trigger"'); instead replace this with a user-facing behavior assertion: check the rendered HTML for the navigation toggle or menu using an accessibility or content-based indicator (e.g., presence of a button or link with an accessible name like "Menu"/"Open navigation", a nav element with visible nav links, or an element with an aria attribute such as aria-expanded/aria-controls) rather than the internal data-slot attribute; update the assertion around the same test block (the expect(html) usage) to assert the user-visible element or behavior instead of the data-slot string.apps/www-start/src/app/(app)/(marketing)/(content)/pricing/pricing-smoke.test.ts (1)
46-47: ⚡ Quick win[warning] Avoid asserting Tailwind spacing tokens in smoke tests.
Lines 46-47 assert concrete utility classes, which makes this test fail on style refactors that do not change user behavior.
As per coding guidelines:
**/*.test.ts: "Prefer behavior-based assertions over implementation details".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/app/`(app)/(marketing)/(content)/pricing/pricing-smoke.test.ts around lines 46 - 47, Replace the fragile Tailwind class assertions (the expect(html).toContain("pt-28") and expect(html).toContain("md:pt-32") checks) with behavior-driven checks against the rendered HTML string: remove those two expects and instead assert on user-facing semantics using the same html variable (for example, assert that key visible content exists such as the pricing heading, plan names, price values or primary CTA text — e.g. expect(html).toContain("Pricing"), expect(html).toContain("Pro"), or expect(html).toContain("$") — or other strings specific to your pricing page). This keeps the test focused on behavior rather than implementation details.apps/www-start/src/app/(app)/(content)/docs/docs-overview-smoke.test.ts (1)
45-46: ⚡ Quick win[warning] Replace class-token assertions with behavior assertions.
Lines 45-46 couple the smoke test to Tailwind implementation details, which makes harmless style refactors fail tests without behavior regressions.
As per coding guidelines:
**/*.test.ts: "Prefer behavior-based assertions over implementation details".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/app/`(app)/(content)/docs/docs-overview-smoke.test.ts around lines 45 - 46, The test is asserting Tailwind class tokens (expect(html).not.toContain("border-r") and expect(html).not.toMatch(/\bborder-b\b/)), which couples it to implementation details; instead locate the target DOM node (use the same rendered output represented by html — e.g., querySelector or testing-library getByRole/getByTestId for the component/container) and assert on behavior: check computed styles (window.getComputedStyle(element).borderRightStyle === 'none' and .borderBottomStyle === 'none' or border widths === '0px') or assert absence/presence of a semantic separator element rather than string-matching class names; replace the two expect(...) lines with those DOM-based assertions.apps/www-start/src/routes/-search-routes.test.ts (1)
12-25: ⚡ Quick win[warning] Replace filesystem-structure checks with route behavior checks.
Lines 15-25 verify file presence, not whether
/searchworks. This is brittle and misses real regressions (render/runtime failures).As per coding guidelines, "
**/*.test.ts: Prefer behavior-based assertions over implementation details".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/routes/-search-routes.test.ts` around lines 12 - 25, The test is checking filesystem presence (fs.existsSync with path.join(appRoot, ...filePaths)) instead of route behavior; remove the loop that inspects those file paths and instead verify the migrated route actually works by exercising routeModules ("./search.tsx") at runtime — e.g., start or simulate the app/router and request/render the /search route (or render the exported route component/loader from routeModules) and assert behavioral outcomes like a 200 response and presence of key UI text/elements (search input placeholder, navbar label, or results container) rather than asserting files exist.apps/www-start/src/routes/-legal-routes.test.ts (1)
3-10: ⚡ Quick win[warning] This test is asserting file layout, not route behavior.
Line 9 hard-codes the module key string; harmless route refactors can fail this test while behavior remains correct. Prefer a behavior-level assertion (e.g., route path handling for a legal slug).
As per coding guidelines, "
**/*.test.ts: Prefer behavior-based assertions over implementation details".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/routes/-legal-routes.test.ts` around lines 3 - 10, The test "registers the migrated dynamic legal route module" is brittle because it asserts the exact module key string; update it to assert behavior instead by using routeModules (the import.meta.glob result) and exercising the exported route handling for a sample slug or by checking keys via a pattern match rather than a hard-coded path: e.g., confirm that routeModules contains at least one key that matches /\/legal\/\$\w+\.tsx$/ or import the module from routeModules["./legal/$slug.tsx"] (or the matched key) and call/inspect its exported handler/component with a representative slug to ensure the route handles a "legal" slug correctly; this keeps the test focused on route behavior (use routeModules, the test case name, and the "./legal/$slug.tsx" module reference to locate and change the assertion).apps/www-start/src/lib/content-feeds.ts (1)
32-33: ⚡ Quick win[warning] Make feed item ordering explicit before truncating to 50 entries.
Right now ordering depends on
getBlogPages()/getChangelogPages()return order. If those helpers change, feed chronology silently regresses.Proposed fix
- for (const page of getBlogPages().slice(0, 50)) { + for (const page of [...getBlogPages()] + .sort( + (a, b) => + new Date(b.data.publishedAt).getTime() - new Date(a.data.publishedAt).getTime() + ) + .slice(0, 50)) { @@ - for (const page of getChangelogPages().slice(0, 50)) { + for (const page of [...getChangelogPages()] + .sort( + (a, b) => + new Date(b.data.publishedAt).getTime() - new Date(a.data.publishedAt).getTime() + ) + .slice(0, 50)) {Also applies to: 76-77
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/lib/content-feeds.ts` around lines 32 - 33, The feed item loops currently truncate to 50 using getBlogPages().slice(0,50) and getChangelogPages().slice(0,50) without an explicit ordering; sort the pages by their publish date in descending order before slicing to ensure stable chronology. Update the code that iterates pages for feed.addItem to first sort the array returned by getBlogPages/getChangelogPages (e.g., compare page.date or page.frontmatter.date timestamps) descending, then take slice(0,50) and map to feed.addItem so the latest items are always used.apps/www-start/src/routes/-seo-discovery-routes.test.ts (1)
3-12: ⚡ Quick win[nit] Prefer asserting discovery endpoint behavior over module-key snapshots.
Lines 10-12 lock test outcomes to file registration details. Assert endpoint contract instead (content type/body expectations for sitemap, robots, llms).
As per coding guidelines, "
**/*.test.ts: Prefer behavior-based assertions over implementation details".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/routes/-seo-discovery-routes.test.ts` around lines 3 - 12, The test currently asserts module key names via routeModules which couples the test to implementation; change it to exercise each discovered module's behavior by iterating Object.values(routeModules) and invoking the exported handlers (e.g., the GET function each module should export) or simulating a request to them, then assert the response contract for sitemap (content-type XML and proper sitemap body), robots (text/plain with expected directives), and llms (text/plain or JSON per spec) instead of comparing file name keys; update the test "registers sitemap, robots, and llms server route modules" to perform these content-type/body assertions against the module exports rather than snapshotting module keys.apps/www-start/src/app/(app)/(marketing)/legal/page.tsx (1)
102-108: 💤 Low valueConsider pinning locale for consistent legal date formatting.
The
undefinedlocale defaults to the user's browser locale, which could display "January 15, 2024" for en-US users but "15 janvier 2024" for fr-FR users. Legal documents typically benefit from consistent date formatting regardless of viewer location.📅 Pin to 'en-US' for consistency
function formatUpdatedAt(updatedAt: string) { - return new Date(updatedAt).toLocaleDateString(undefined, { + return new Date(updatedAt).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", }); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/app/`(app)/(marketing)/legal/page.tsx around lines 102 - 108, The formatUpdatedAt function currently uses new Date(updatedAt).toLocaleDateString(undefined, ...) which lets the runtime locale vary per user; change the locale argument to a fixed locale (e.g., 'en-US') so formatUpdatedAt consistently produces the same legal date format across viewers, keeping the existing options (year, month, day).apps/www-start/src/routes/-use-cases-routes.test.ts (1)
8-14: ⚡ Quick win[warning] Avoid exact module-key snapshots; assert route contract behavior instead.
Line 9 hard-codes Vite glob output, so harmless file moves/renames can fail this test without user-facing impact. Prefer asserting required route modules are present and export the expected route contract.
As per coding guidelines: "
**/*.test.ts: Prefer behavior-based assertions over implementation details".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/routes/-use-cases-routes.test.ts` around lines 8 - 14, The test "registers the migrated use-case route modules" currently asserts an exact array of Vite glob keys from routeModules; instead change it to behavior-based checks: iterate over the expected module identifiers (e.g., the four use-case entries) and assert each one is a key in Object.keys(routeModules) and that the imported module (routeModules[MODULE_KEY]) exports the expected route contract shape (e.g., required properties or functions your app expects, such as a default export or a route object with loader/action/handle). Update the expectation logic in the test to use presence checks and contract assertions (property existence/type checks) rather than strict equality of the full keys array.apps/www-start/src/lib/root-head.test.ts (1)
5-24: ⚡ Quick win[warning] Add coverage for the
stylesheetHrefbranch.Current test only validates the default path. Line 1 in the implementation has a conditional links branch that remains unverified.
Suggested test addition
describe("buildRootHead", () => { it("provides the baseline SEO and app shell links", () => { @@ }); + + it("includes stylesheet link when stylesheetHref is provided", () => { + const head = buildRootHead("/assets/app.css"); + expect(head.links).toContainEqual({ + rel: "stylesheet", + href: "/assets/app.css", + }); + }); });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/lib/root-head.test.ts` around lines 5 - 24, Add a test that exercises the stylesheetHref branch of buildRootHead by calling buildRootHead with a stylesheetHref value (e.g., '/styles.css') and assert the returned head.links includes { rel: "stylesheet", href: "/styles.css" }; also keep existing assertions (canonical/manifest) to ensure other links remain. Locate the function buildRootHead in the test and add a new it(...) that passes the stylesheetHref option and checks for the stylesheet link.apps/www-start/src/app/(app)/_components/latest-content-preview.tsx (1)
66-68: ⚡ Quick win[warning] Add
dateTimeon<time>elements.Line 66 and Line 90 render human-readable dates only; missing
dateTimeweakens semantics and machine parsing.Suggested patch
- <time className="text-muted-foreground text-sm"> + <time className="text-muted-foreground text-sm" dateTime={item.publishedAt}> {formatDate(item.publishedAt)} </time> ... - <time className="text-muted-foreground text-sm"> + <time className="text-muted-foreground text-sm" dateTime={item.publishedAt}> {formatDate(item.publishedAt)} </time>Also applies to: 90-92
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www-start/src/app/`(app)/_components/latest-content-preview.tsx around lines 66 - 68, The <time> elements in the latest-content-preview component render only human-readable dates; add a dateTime attribute so machines can parse the date. Update the <time> that uses formatDate(item.publishedAt) to include dateTime={new Date(item.publishedAt).toISOString()} (or dateTime={item.publishedAt} if already ISO), and do the same for the other <time> instance around lines 90-92 (e.g., if it shows item.updatedAt use new Date(item.updatedAt).toISOString()). Keep the visible text as formatDate(...) and only add the dateTime prop.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3421bbd1-8332-446f-807a-b0d11feeeac7
⛔ Files ignored due to path filters (26)
apps/www-start/public/android-chrome-192x192.pngis excluded by!**/*.png,!**/*.pngapps/www-start/public/android-chrome-512x512.pngis excluded by!**/*.png,!**/*.pngapps/www-start/public/apple-touch-icon.pngis excluded by!**/*.png,!**/*.pngapps/www-start/public/favicon-16x16.pngis excluded by!**/*.png,!**/*.pngapps/www-start/public/favicon-32x32.pngis excluded by!**/*.png,!**/*.pngapps/www-start/public/favicon-48x48.pngis excluded by!**/*.png,!**/*.pngapps/www-start/public/favicon.icois excluded by!**/*.ico,!**/*.icoapps/www-start/public/favicon.svgis excluded by!**/*.svg,!**/*.svgapps/www-start/public/fonts/geist/Geist-Variable.woff2is excluded by!**/*.woff2,!**/*.woff2apps/www-start/public/fonts/geist/GeistMono-Variable.woff2is excluded by!**/*.woff2,!**/*.woff2apps/www-start/public/fonts/pp-neue-montreal/PPNeueMontreal-Book.woff2is excluded by!**/*.woff2,!**/*.woff2apps/www-start/public/fonts/pp-neue-montreal/PPNeueMontreal-Medium.woff2is excluded by!**/*.woff2,!**/*.woff2apps/www-start/public/images/announcing-lightfast.webpis excluded by!**/*.webpapps/www-start/public/images/blog/why-we-built-lightfast.pngis excluded by!**/*.png,!**/*.pngapps/www-start/public/images/blog/why-we-built-lightfast.webpis excluded by!**/*.webpapps/www-start/public/images/changelog/v010-events.pngis excluded by!**/*.png,!**/*.pngapps/www-start/public/images/changelog/v010-events.webpis excluded by!**/*.webpapps/www-start/public/images/changelog/v010-featured.pngis excluded by!**/*.png,!**/*.pngapps/www-start/public/images/changelog/v010-featured.webpis excluded by!**/*.webpapps/www-start/public/images/changelog/v010-sdk-mcp.pngis excluded by!**/*.png,!**/*.pngapps/www-start/public/images/changelog/v010-sdk-mcp.webpis excluded by!**/*.webpapps/www-start/public/images/changelog/v010-sources.pngis excluded by!**/*.png,!**/*.pngapps/www-start/public/images/changelog/v010-sources.webpis excluded by!**/*.webpapps/www-start/public/images/nascent_remix.webpis excluded by!**/*.webpapps/www-start/src/env.d.tsis excluded by!**/*.d.tspnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!**/pnpm-lock.yaml
📒 Files selected for processing (156)
apps/app/microfrontends.jsonapps/app/package.jsonapps/app/src/__tests__/microfrontends-config.test.tsapps/www-start/env.build.tsapps/www-start/package.jsonapps/www-start/portless.jsonapps/www-start/public/manifest.jsonapps/www-start/src/app/(app)/(company)/_components/company-navbar.tsxapps/www-start/src/app/(app)/(company)/careers/careers-smoke.test.tsapps/www-start/src/app/(app)/(company)/careers/page.tsxapps/www-start/src/app/(app)/(company)/company/_components/manifesto-shader.tsxapps/www-start/src/app/(app)/(company)/company/company-smoke.test.tsapps/www-start/src/app/(app)/(company)/company/layout.tsxapps/www-start/src/app/(app)/(company)/company/page.tsxapps/www-start/src/app/(app)/(company)/layout.tsxapps/www-start/src/app/(app)/(content)/docs/(general)/[[...slug]]/_components/developer-platform-landing.tsxapps/www-start/src/app/(app)/(content)/docs/_components/alpha-banner.tsxapps/www-start/src/app/(app)/(content)/docs/docs-overview-smoke.test.tsapps/www-start/src/app/(app)/(content)/docs/docs-shell.tsxapps/www-start/src/app/(app)/(marketing)/(content)/blog/(listing)/_components/blog-listing-header.tsxapps/www-start/src/app/(app)/(marketing)/(content)/blog/(listing)/layout.tsxapps/www-start/src/app/(app)/(marketing)/(content)/blog/(listing)/page.tsxapps/www-start/src/app/(app)/(marketing)/(content)/blog/(listing)/topic/[category]/page.tsxapps/www-start/src/app/(app)/(marketing)/(content)/blog/[slug]/page.tsxapps/www-start/src/app/(app)/(marketing)/(content)/blog/layout.tsxapps/www-start/src/app/(app)/(marketing)/(content)/changelog/[slug]/page.tsxapps/www-start/src/app/(app)/(marketing)/(content)/changelog/_components/changelog-improvements.tsxapps/www-start/src/app/(app)/(marketing)/(content)/changelog/layout.tsxapps/www-start/src/app/(app)/(marketing)/(content)/changelog/page.tsxapps/www-start/src/app/(app)/(marketing)/(content)/content-markdown.tsxapps/www-start/src/app/(app)/(marketing)/(content)/json-ld-script.tsxapps/www-start/src/app/(app)/(marketing)/(content)/pricing/page.tsxapps/www-start/src/app/(app)/(marketing)/(content)/pricing/pricing-smoke.test.tsapps/www-start/src/app/(app)/(marketing)/(content)/use-cases/agent-builders/data.tsapps/www-start/src/app/(app)/(marketing)/(content)/use-cases/agent-builders/page.tsxapps/www-start/src/app/(app)/(marketing)/(content)/use-cases/engineering-leaders/data.tsapps/www-start/src/app/(app)/(marketing)/(content)/use-cases/engineering-leaders/page.tsxapps/www-start/src/app/(app)/(marketing)/(content)/use-cases/platform-engineers/data.tsapps/www-start/src/app/(app)/(marketing)/(content)/use-cases/platform-engineers/page.tsxapps/www-start/src/app/(app)/(marketing)/(content)/use-cases/technical-founders/data.tsapps/www-start/src/app/(app)/(marketing)/(content)/use-cases/technical-founders/page.tsxapps/www-start/src/app/(app)/(marketing)/(content)/use-cases/use-cases-smoke.test.tsapps/www-start/src/app/(app)/(marketing)/(landing)/_components/isometric-hero.tsxapps/www-start/src/app/(app)/(marketing)/(landing)/landing-smoke.test.tsapps/www-start/src/app/(app)/(marketing)/(landing)/page.tsxapps/www-start/src/app/(app)/(marketing)/layout.tsxapps/www-start/src/app/(app)/(marketing)/legal/layout.tsxapps/www-start/src/app/(app)/(marketing)/legal/legal-smoke.test.tsapps/www-start/src/app/(app)/(marketing)/legal/page.tsxapps/www-start/src/app/(app)/(search)/layout.tsxapps/www-start/src/app/(app)/(search)/search/page.tsxapps/www-start/src/app/(app)/_components/app-footer.tsxapps/www-start/src/app/(app)/_components/app-mobile-nav-lazy.tsxapps/www-start/src/app/(app)/_components/app-mobile-nav.tsxapps/www-start/src/app/(app)/_components/app-navbar-menu.tsxapps/www-start/src/app/(app)/_components/app-navbar.tsxapps/www-start/src/app/(app)/_components/blog-category-dropdown.tsxapps/www-start/src/app/(app)/_components/blog-category-nav.tsxapps/www-start/src/app/(app)/_components/blog-social-share.tsxapps/www-start/src/app/(app)/_components/faq-accordion.tsxapps/www-start/src/app/(app)/_components/faq-section.tsxapps/www-start/src/app/(app)/_components/get-started-cta.tsxapps/www-start/src/app/(app)/_components/hero-changelog-badge.tsxapps/www-start/src/app/(app)/_components/latest-content-preview.tsxapps/www-start/src/app/(app)/_components/search-input.tsxapps/www-start/src/app/(app)/_components/search-interface.tsxapps/www-start/src/app/(app)/_components/search-navbar.tsxapps/www-start/src/app/(app)/_components/search-results.tsxapps/www-start/src/app/(app)/_components/use-case-grid.tsxapps/www-start/src/app/(app)/_hooks/use-text-cycle.tsapps/www-start/src/components/nav-link.tsxapps/www-start/src/config/nav.tsapps/www-start/src/content/blog/2026-03-26-why-we-built-lightfast.mdxapps/www-start/src/content/changelog/2026-03-26-lightfast-engineering-intelligence-shipped.mdxapps/www-start/src/content/docs/get-started/meta.jsonapps/www-start/src/content/docs/get-started/overview.mdxapps/www-start/src/content/docs/integrate/meta.jsonapps/www-start/src/content/docs/meta.jsonapps/www-start/src/content/legal/privacy.mdxapps/www-start/src/content/legal/terms.mdxapps/www-start/src/env/client.tsapps/www-start/src/lib/blog-content.test.tsapps/www-start/src/lib/blog-content.tsapps/www-start/src/lib/changelog-content.test.tsapps/www-start/src/lib/changelog-content.tsapps/www-start/src/lib/content-assets.tsapps/www-start/src/lib/content-common.tsapps/www-start/src/lib/content-feeds.test.tsapps/www-start/src/lib/content-feeds.tsapps/www-start/src/lib/content-schemas.tsapps/www-start/src/lib/docs-content.test.tsapps/www-start/src/lib/docs-content.tsapps/www-start/src/lib/landing-content.test.tsapps/www-start/src/lib/landing-content.tsapps/www-start/src/lib/legal-content.test.tsapps/www-start/src/lib/legal-content.tsapps/www-start/src/lib/pricing-content.tsapps/www-start/src/lib/public-manifest.test.tsapps/www-start/src/lib/root-head.test.tsapps/www-start/src/lib/root-head.tsapps/www-start/src/lib/search-content.tsapps/www-start/src/lib/seo-discovery-retired-url.test.tsapps/www-start/src/lib/seo-discovery.test.tsapps/www-start/src/lib/seo-discovery.tsapps/www-start/src/lib/use-cases-content.tsapps/www-start/src/routeTree.gen.tsapps/www-start/src/router.tsxapps/www-start/src/routes/-company-routes.test.tsapps/www-start/src/routes/-content-routes.test.tsapps/www-start/src/routes/-docs-routes.test.tsapps/www-start/src/routes/-legal-routes.test.tsapps/www-start/src/routes/-search-routes.test.tsapps/www-start/src/routes/-seo-discovery-routes.test.tsapps/www-start/src/routes/-use-cases-routes.test.tsapps/www-start/src/routes/__root.tsxapps/www-start/src/routes/blog.tsxapps/www-start/src/routes/blog/$slug.tsxapps/www-start/src/routes/blog/atom[.]xml.tsapps/www-start/src/routes/blog/feed[.]xml.tsapps/www-start/src/routes/blog/rss[.]xml.tsapps/www-start/src/routes/blog/topic/$category.tsxapps/www-start/src/routes/careers.tsxapps/www-start/src/routes/changelog.tsxapps/www-start/src/routes/changelog/$slug.tsxapps/www-start/src/routes/changelog/atom[.]xml.tsapps/www-start/src/routes/changelog/feed[.]xml.tsapps/www-start/src/routes/changelog/rss[.]xml.tsapps/www-start/src/routes/company.tsxapps/www-start/src/routes/docs/$.tsxapps/www-start/src/routes/index.tsxapps/www-start/src/routes/legal/$slug.tsxapps/www-start/src/routes/llms[.]txt.tsapps/www-start/src/routes/pricing.tsxapps/www-start/src/routes/robots[.]txt.tsapps/www-start/src/routes/search.tsxapps/www-start/src/routes/sitemap[.]xml.tsapps/www-start/src/routes/use-cases/agent-builders.tsxapps/www-start/src/routes/use-cases/engineering-leaders.tsxapps/www-start/src/routes/use-cases/platform-engineers.tsxapps/www-start/src/routes/use-cases/technical-founders.tsxapps/www-start/src/styles/globals.cssapps/www-start/src/types/nav.tsapps/www-start/tsconfig.jsonapps/www-start/turbo.jsonapps/www-start/vite.config.tsapps/www-start/vitest.config.tsapps/www/package.jsonapps/www/src/app/(app)/(marketing)/(content)/blog/(listing)/topic/[category]/page.tsxapps/www/src/app/(app)/(marketing)/(content)/changelog/page.tsxapps/www/src/app/(app)/(search)/search/page.tsxapps/www/src/app/(seo)/llms.txt/route.tsapps/www/src/app/robots.tsapps/www/src/app/sitemap.test.tsapps/www/src/app/sitemap.tspackage.jsonpnpm-workspace.yaml
💤 Files with no reviewable changes (13)
- apps/www-start/src/app/(app)/_components/search-input.tsx
- apps/www/src/app/(app)/(marketing)/(content)/changelog/page.tsx
- apps/www/src/app/sitemap.ts
- apps/www-start/src/app/(app)/_components/blog-social-share.tsx
- apps/www-start/src/app/(app)/_components/search-navbar.tsx
- apps/www/src/app/robots.ts
- apps/www/src/app/(seo)/llms.txt/route.ts
- apps/www/src/app/(app)/(search)/search/page.tsx
- apps/www-start/src/app/(app)/_components/search-results.tsx
- apps/www-start/src/app/(app)/_components/search-interface.tsx
- apps/www/src/app/sitemap.test.ts
- apps/www-start/src/app/(app)/(marketing)/(content)/changelog/_components/changelog-improvements.tsx
- apps/www/src/app/(app)/(marketing)/(content)/blog/(listing)/topic/[category]/page.tsx
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
apps/www/src/types/nav.ts (1)
3-8: ⚡ Quick winDocument the type safety trade-off for future cleanup.
Changing
InternalNavItem<Route>toInternalNavItem<string>removes compile-time route validation. The comment explains the migration need, but this trade-off should ideally be tracked for restoration once the www-start migration is complete and route ownership stabilizes.Consider adding a TODO or linking a follow-up issue to restore
Routetyping after the migration completes.Also applies to: 23-23
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www/src/types/nav.ts` around lines 3 - 8, Add a short TODO comment and/or reference to a follow-up issue next to the InternalNavItem type to document the deliberate loss of compile-time route validation (changed from InternalNavItem<Route> to InternalNavItem<string>), so future maintainers know to restore the Route typing after the www-start migration completes; mention the exact symbol InternalNavItem (and BaseNavItem/Route) and include a ticket number or TODO marker like "TODO: restore Route typing once apps/www-start migration stabilizes" so this trade-off is tracked for later cleanup.apps/www/src/components/nav-link.tsx (1)
4-4: ⚡ Quick winType safety degraded by Route → string migration.
The
as Routecast defeats Next.js compile-time route validation. Invalid route strings won't be caught until runtime. This is necessary givenInternalNavItem<string>in types/nav.ts, but it's a trade-off worth documenting or planning to restore post-migration.Consider adding a TODO comment or migration tracking issue to restore typed routes once the www-start transition completes.
Also applies to: 100-100
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/www/src/components/nav-link.tsx` at line 4, The import and use of Route in nav-link.tsx currently relies on an as Route cast (and the InternalNavItem<string> in types/nav.ts), which degrades compile-time route validation; add a concise TODO comment next to the Route import and/or the cast in NavLink (or where you use the as Route) stating that this is a temporary migration workaround and include a tracking issue ID or TODO/JIRA reference to restore typed Next.js routes after the www-start migration, so future maintainers know to revert the cast and reintroduce strict Route typing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/www/src/components/nav-link.tsx`:
- Around line 57-60: getPathname currently returns path.split(...)[0] which can
be an empty string (e.g. for "`#foo`") so the existing "?? '/'" fallback never
triggers; update getPathname to treat an empty string as missing and return "/"
instead (e.g. compute the first segment into a variable and return that variable
=== "" ? "/" : variable or use a falsy-coalescing check) so hash-only hrefs
normalize to "/" rather than ""—apply this change inside the getPathname
function.
---
Nitpick comments:
In `@apps/www/src/components/nav-link.tsx`:
- Line 4: The import and use of Route in nav-link.tsx currently relies on an as
Route cast (and the InternalNavItem<string> in types/nav.ts), which degrades
compile-time route validation; add a concise TODO comment next to the Route
import and/or the cast in NavLink (or where you use the as Route) stating that
this is a temporary migration workaround and include a tracking issue ID or
TODO/JIRA reference to restore typed Next.js routes after the www-start
migration, so future maintainers know to revert the cast and reintroduce strict
Route typing.
In `@apps/www/src/types/nav.ts`:
- Around line 3-8: Add a short TODO comment and/or reference to a follow-up
issue next to the InternalNavItem type to document the deliberate loss of
compile-time route validation (changed from InternalNavItem<Route> to
InternalNavItem<string>), so future maintainers know to restore the Route typing
after the www-start migration completes; mention the exact symbol
InternalNavItem (and BaseNavItem/Route) and include a ticket number or TODO
marker like "TODO: restore Route typing once apps/www-start migration
stabilizes" so this trade-off is tracked for later cleanup.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 92c6c485-2f07-4bb4-9493-e0662aebb2b9
⛔ Files ignored due to path filters (10)
apps/www/public/images/pitch-deck-anthropic-visual.pngis excluded by!**/*.png,!**/*.pngapps/www/public/images/pitch-deck/Slide 01 — Title@2x (2).pngis excluded by!**/*.png,!**/*.pngapps/www/public/images/pitch-deck/Slide 02 — Problem@2x (2).pngis excluded by!**/*.png,!**/*.pngapps/www/public/images/pitch-deck/Slide 03 — Solution@2x (2).pngis excluded by!**/*.png,!**/*.pngapps/www/public/images/pitch-deck/Slide 04 — Why Now@2x (2).pngis excluded by!**/*.png,!**/*.pngapps/www/public/images/pitch-deck/Slide 05 — Market@2x (2).pngis excluded by!**/*.png,!**/*.pngapps/www/public/images/pitch-deck/Slide 06 — Competition@2x (2).pngis excluded by!**/*.png,!**/*.pngapps/www/public/images/pitch-deck/Slide 07 — Operating Landscape@2x (2).pngis excluded by!**/*.png,!**/*.pngapps/www/public/images/pitch-deck/Slide 08 — Product@2x (1).pngis excluded by!**/*.png,!**/*.pngapps/www/public/images/pitch-deck/Slide 10 — Founder@2x (2).pngis excluded by!**/*.png,!**/*.png
📒 Files selected for processing (34)
apps/app/microfrontends.jsonapps/app/src/__tests__/microfrontends-config.test.tsapps/www-start/src/config/nav.tsapps/www-start/src/lib/seo-discovery.test.tsapps/www-start/src/lib/seo-discovery.tsapps/www/src/app/(app)/(internal)/pitch-deck/_components/capture-slide.tsxapps/www/src/app/(app)/(internal)/pitch-deck/_components/download-button.tsxapps/www/src/app/(app)/(internal)/pitch-deck/_components/landscape-prompt-modal.tsxapps/www/src/app/(app)/(internal)/pitch-deck/_components/mobile-bottom-bar.tsxapps/www/src/app/(app)/(internal)/pitch-deck/_components/pitch-deck-context.tsxapps/www/src/app/(app)/(internal)/pitch-deck/_components/pitch-deck-layout-content.tsxapps/www/src/app/(app)/(internal)/pitch-deck/_components/pitch-deck-mobile-nav.tsxapps/www/src/app/(app)/(internal)/pitch-deck/_components/pitch-deck.tsxapps/www/src/app/(app)/(internal)/pitch-deck/_components/preface-toggle.tsxapps/www/src/app/(app)/(internal)/pitch-deck/_components/slide-content/columns-slide-content.tsxapps/www/src/app/(app)/(internal)/pitch-deck/_components/slide-content/content-slide-content.tsxapps/www/src/app/(app)/(internal)/pitch-deck/_components/slide-content/custom-team-slide.tsxapps/www/src/app/(app)/(internal)/pitch-deck/_components/slide-content/custom-title-slide.tsxapps/www/src/app/(app)/(internal)/pitch-deck/_components/slide-content/custom-why-now-slide.tsxapps/www/src/app/(app)/(internal)/pitch-deck/_components/slide-content/index.tsapps/www/src/app/(app)/(internal)/pitch-deck/_components/slide-content/resolve-slide-component.tsxapps/www/src/app/(app)/(internal)/pitch-deck/_components/slide-content/showcase-slide-content.tsxapps/www/src/app/(app)/(internal)/pitch-deck/_components/slide-content/title-slide-content.tsxapps/www/src/app/(app)/(internal)/pitch-deck/_lib/animation-utils.test.tsapps/www/src/app/(app)/(internal)/pitch-deck/_lib/animation-utils.tsapps/www/src/app/(app)/(internal)/pitch-deck/_lib/export-slides-lazy.tsapps/www/src/app/(app)/(internal)/pitch-deck/_lib/export-slides.tsapps/www/src/app/(app)/(internal)/pitch-deck/_lib/motion-features.tsapps/www/src/app/(app)/(internal)/pitch-deck/layout.tsxapps/www/src/app/(app)/(internal)/pitch-deck/page.tsxapps/www/src/components/nav-link.tsxapps/www/src/config/nav.tsapps/www/src/config/pitch-deck-data.tsapps/www/src/types/nav.ts
💤 Files with no reviewable changes (30)
- apps/www/src/app/(app)/(internal)/pitch-deck/_components/slide-content/index.ts
- apps/www/src/app/(app)/(internal)/pitch-deck/_components/slide-content/custom-title-slide.tsx
- apps/www/src/app/(app)/(internal)/pitch-deck/_components/slide-content/title-slide-content.tsx
- apps/www/src/app/(app)/(internal)/pitch-deck/_components/pitch-deck-context.tsx
- apps/www/src/app/(app)/(internal)/pitch-deck/_components/mobile-bottom-bar.tsx
- apps/www-start/src/config/nav.ts
- apps/www/src/app/(app)/(internal)/pitch-deck/_components/download-button.tsx
- apps/www/src/app/(app)/(internal)/pitch-deck/_components/slide-content/resolve-slide-component.tsx
- apps/www/src/app/(app)/(internal)/pitch-deck/_components/preface-toggle.tsx
- apps/www/src/app/(app)/(internal)/pitch-deck/_lib/export-slides.ts
- apps/www/src/config/pitch-deck-data.ts
- apps/www/src/app/(app)/(internal)/pitch-deck/_components/slide-content/content-slide-content.tsx
- apps/www/src/app/(app)/(internal)/pitch-deck/_components/slide-content/custom-why-now-slide.tsx
- apps/www/src/app/(app)/(internal)/pitch-deck/_components/capture-slide.tsx
- apps/www/src/app/(app)/(internal)/pitch-deck/_components/slide-content/showcase-slide-content.tsx
- apps/www/src/app/(app)/(internal)/pitch-deck/_components/slide-content/custom-team-slide.tsx
- apps/www/src/app/(app)/(internal)/pitch-deck/_components/pitch-deck.tsx
- apps/www/src/app/(app)/(internal)/pitch-deck/_components/pitch-deck-layout-content.tsx
- apps/www/src/app/(app)/(internal)/pitch-deck/_components/slide-content/columns-slide-content.tsx
- apps/www/src/app/(app)/(internal)/pitch-deck/_components/pitch-deck-mobile-nav.tsx
- apps/www/src/app/(app)/(internal)/pitch-deck/_lib/animation-utils.ts
- apps/www/src/app/(app)/(internal)/pitch-deck/page.tsx
- apps/www/src/app/(app)/(internal)/pitch-deck/_components/landscape-prompt-modal.tsx
- apps/www/src/app/(app)/(internal)/pitch-deck/_lib/export-slides-lazy.ts
- apps/www/src/app/(app)/(internal)/pitch-deck/layout.tsx
- apps/www/src/app/(app)/(internal)/pitch-deck/_lib/motion-features.ts
- apps/www/src/app/(app)/(internal)/pitch-deck/_lib/animation-utils.test.ts
- apps/app/microfrontends.json
- apps/www/src/config/nav.ts
- apps/www-start/src/lib/seo-discovery.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/app/src/tests/microfrontends-config.test.ts
- apps/www-start/src/lib/seo-discovery.test.ts
Summary
@lightfast/www-startas the TanStack Start marketing app with route wrappers, shared UI/content helpers, assets, and Portless/Vite wiring.www-start: home, pricing, use cases, legal, company/careers, docs overview, search, blog, changelog, feeds, sitemap, robots, and llms.txt.apps/wwwlinks for migrated paths through the MFE link helper so old Next still builds while the aggregate owns those paths./pitch-deckroute tree/assets and stale/integrationsMFE route ownership instead of migrating those surfaces.www-startfor Vercel TanStack Start deployment with Nitro,vercel.json, and related-project metadata.Test Plan
pnpm checkpnpm --filter @lightfast/app exec vitest run src/__tests__/microfrontends-config.test.tspnpm --filter @lightfast/app typecheckpnpm build:apppnpm --filter @lightfast/www-start testpnpm --filter @lightfast/www-start typecheckpnpm --filter @lightfast/www-start buildcd apps/www && pnpm with-env next typegen && pnpm typecheck && pnpm buildNotes
lightfast-www-startwas created asprj_e7coTbiIunVOLBYIKLwcC3CUJgsw; adding it to the existing MFE group requires interactive billing confirmation by a human.Summary by CodeRabbit