Website v1.5: AI-native copy revamp, new pages, and UX improvements#3086
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review infoConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds many Next.js pages and layouts (features, workflows, site‑map, about, faq), new hero components, widespread landing copy and spacing updates, a FAQDropdown refactor (FAQItem with measured heights and animations), new routes/constants and sitemap updates, and a minor preload‑script syntax tweak. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant WorkflowsPage
participant ScrollSpy
participant SectionNav
User->>WorkflowsPage: GET /workflows (render)
WorkflowsPage->>WorkflowsPage: mount hero, workflow cards, SectionNav
WorkflowsPage->>ScrollSpy: init viewport observer (useEffect)
User->>WorkflowsPage: scroll
ScrollSpy->>SectionNav: update currentSection state
SectionNav->>User: highlight active section
User->>SectionNav: click section link
SectionNav->>WorkflowsPage: invoke scrollToSection(anchor)
WorkflowsPage->>User: smooth scroll to anchor
ScrollSpy->>SectionNav: update active section after scroll
sequenceDiagram
participant User
participant FAQDropdown
participant FAQItem
participant RefMeasure
User->>FAQDropdown: view FAQ list
FAQDropdown->>FAQItem: render items (closed)
User->>FAQItem: click toggle
FAQItem->>RefMeasure: read content.scrollHeight
RefMeasure->>FAQItem: return height
FAQItem->>FAQItem: set maxHeight & isOpen -> animate expand
User->>FAQItem: click toggle again
FAQItem->>FAQItem: set maxHeight=0 -> animate collapse
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Note
Due to the large number of review comments, Critical 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 (3)
apps/web/client/src/utils/constants/navigation.ts (1)
16-40:⚠️ Potential issue | 🟠 MajorUse localized message keys for the new navigation labels/descriptions.
These newly added
title/descriptionvalues are user-facing strings hardcoded in constants.As per coding guidelines:
apps/web/client/src/**/*.{ts,tsx}: Avoid hardcoded user-facing text; use next-intl messages/hooks.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/utils/constants/navigation.ts` around lines 16 - 40, Replace the hardcoded user-facing strings in the navigation entries with next-intl message keys and use the translation hook/loader when rendering; specifically swap the literal title/description values for the entries referencing Routes.FEATURES_AI_FRONTEND, Routes.FEATURES_BUILDER, Routes.FEATURES_PROTOTYPE, Routes.WORKFLOWS_CLAUDE_CODE, and Routes.WORKFLOWS_VIBE_CODING to message IDs (e.g. navigation.aiFrontend.title / .description etc.), add those keys to the locale message files, and ensure the component that consumes this constants file calls the next-intl hook (or passes translated strings) instead of using the hardcoded text.apps/web/client/src/app/faq/page.tsx (2)
147-167:⚠️ Potential issue | 🟡 MinorUse a stable scroll handler effect (no
currentSectiondependency).The current dependency array causes the window listener to be reattached as sections change.
💡 Suggested fix
- useEffect(() => { + useEffect(() => { const handleScroll = () => { const offset = 120; let activeIdx = 0; for (let i = 0; i < sectionRefs.current.length; i++) { const ref = sectionRefs.current[i]; if (ref) { const top = ref.getBoundingClientRect().top; if (top <= offset) { activeIdx = i; } } } - if (faqSections[activeIdx]?.anchor && faqSections[activeIdx]?.anchor !== currentSection) { - setCurrentSection(faqSections[activeIdx]?.anchor || ''); - } + const next = faqSections[activeIdx]?.anchor || ''; + setCurrentSection(prev => (prev === next ? prev : next)); }; window.addEventListener('scroll', handleScroll, { passive: true }); handleScroll(); return () => window.removeEventListener('scroll', handleScroll); - }, [currentSection]); + }, []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/faq/page.tsx` around lines 147 - 167, The effect currently re-attaches the scroll listener because it depends on currentSection; change the useEffect dependency array to [] (stable on mount) and modify the handler so it no longer reads currentSection directly: compute newAnchor from sectionRefs.current and faqSections, then call setCurrentSection(prev => (newAnchor && newAnchor !== prev) ? newAnchor : prev). Keep the window.addEventListener/removeEventListener logic and the initial handleScroll() call; reference the existing handleScroll function, sectionRefs, faqSections, setCurrentSection and currentSection to locate and update the code.
11-119:⚠️ Potential issue | 🟠 MajorLocalize the new FAQ/summary/CTA copy through message catalogs.
The newly added user-facing strings are hardcoded throughout this page.
As per coding guidelines
apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.Also applies to: 186-203, 209-222, 247-248
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/faq/page.tsx` around lines 11 - 119, The FAQ page currently hardcodes all user-facing strings (section "title", "anchor", and each "faqs" array's "question" and "answer"); extract these into next-intl message catalogs and replace inline literals with translations via the next-intl hook (e.g., useTranslations('faq') or a suitable namespace) so titles become t('aboutOnlook.title'), questions t('aboutOnlook.faqs.0.question') and answers t('aboutOnlook.faqs.0.answer'); add corresponding keys in the locale JSON files for each section (about-onlook, features, compatibility, workflow, company) and update any CTA/summary strings referenced (the same pattern) so all user-facing text is loaded from the message catalog instead of hardcoded in the page.tsx data structures.
🟠 Major comments (28)
apps/web/client/src/app/_components/landing-page/features-intro-section.tsx-8-15 (1)
8-15:⚠️ Potential issue | 🟠 MajorExternalize landing copy to
next-intlkeys.These new strings are hardcoded user-facing text. Please move them to message keys and read via
next-intlin this component.Suggested refactor
+import { getTranslations } from 'next-intl/server'; import React from 'react'; -export function FeaturesIntroSection() { +export async function FeaturesIntroSection() { + const t = await getTranslations('landing.featuresIntro'); return ( <div className="w-full max-w-6xl mx-auto py-32 px-8 text-center"> <div className="max-w-3xl mx-auto"> <h2 className="text-foreground-secondary text-sm font-medium uppercase tracking-wider mb-6"> - Native Design Tool Features + {t('eyebrow')} </h2> <p className="text-foreground-primary text-2xl md:text-5xl leading-[1.1] font-light mb-8 text-balance"> - Familiar to Designers. Trusted by Engineers. + {t('title')} </p> <p className="text-foreground-secondary text-lg max-w-xl mx-auto text-balance"> - A canvas that feels intuitive, with real code underneath. Engineers can merge what you create directly — no handoff, no rebuilding. + {t('description')} </p> </div> </div> ); }As per coding guidelines:
apps/web/client/src/app/**/*.tsx: “Avoid hardcoded user-facing text; use next-intl messages/hooks”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/_components/landing-page/features-intro-section.tsx` around lines 8 - 15, Replace the hardcoded headings and paragraphs in the FeaturesIntroSection component with next-intl message lookups: import and call useTranslations (or the existing i18n hook) at the top of the component, replace the literal strings ("Native Design Tool Features", "Familiar to Designers. Trusted by Engineers.", and the longer paragraph about the canvas) with t('landing.features.title'), t('landing.features.tagline'), and t('landing.features.description') (or similarly named keys), and add those keys to the locale message files used by the app; ensure you keep the existing className props and markup (h2 and p elements) and only swap the inner text for the t(...) calls so formatting/layout is unchanged.apps/web/client/src/app/_components/landing-page/cta-section.tsx-15-15 (1)
15-15:⚠️ Potential issue | 🟠 MajorMove default CTA copy out of code and into i18n messages.
Line 15 hardcodes user-facing fallback text in this app-route component, which violates the repo i18n rule and makes localization brittle.
💡 Suggested patch
import { Button } from '@onlook/ui/button'; +import { useTranslations } from 'next-intl'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; @@ -export function CTASection({ href, onClick, ctaText = "Ready to stop rebuilding?\nYour design system, on a canvas.", buttonText = "Book a Demo", showSubtext = true }: CTASectionProps = {}) { +export function CTASection({ href, onClick, ctaText, buttonText, showSubtext = true }: CTASectionProps = {}) { + const t = useTranslations('landingPage.ctaSection'); + const resolvedCtaText = ctaText ?? t('ctaText'); + const resolvedButtonText = buttonText ?? t('buttonText'); const router = useRouter(); @@ - {ctaText.split('\n').map((line, index) => ( + {resolvedCtaText.split('\n').map((line, index) => ( <span key={index}> {line} - {index < ctaText.split('\n').length - 1 && <br />} + {index < resolvedCtaText.split('\n').length - 1 && <br />} </span> ))} @@ - {buttonText} + {resolvedButtonText} </Button>As per coding guidelines:
apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/_components/landing-page/cta-section.tsx` at line 15, The CTASection function currently hardcodes user-facing defaults (ctaText and buttonText); move those strings into i18n message keys (e.g. "cta.ctaText" and "cta.buttonText") in the appropriate messages file and replace the hardcoded defaults by calling the next-intl translation hook (e.g. useTranslations or equivalent) inside CTASection to supply the fallback values; update CTASectionProps to accept optional strings and use the translator to compute final values for ctaText and buttonText (and any subtext) so all user-facing copy comes from locale messages rather than inline literals.apps/web/client/src/app/_components/landing-page/builder-features-intro-section.tsx-8-15 (1)
8-15:⚠️ Potential issue | 🟠 MajorMove landing copy to
next-intlmessage keys.Lines 8, 11, and 14 contain hardcoded user-facing text, which violates the guideline for
app/**/*.tsxcomponents. Extract these strings to translation messages and useuseTranslationshook for consistent i18n handling.Add the required message keys to
apps/web/client/messages/en.json(and other language files) under alanding.builderFeaturesIntroSectionnamespace, then refactor the component to useuseTranslations:Suggested refactor
import React from 'react'; +import { useTranslations } from 'next-intl'; export function BuilderFeaturesIntroSection() { + const t = useTranslations('landing.builderFeaturesIntroSection'); return ( <div className="w-full max-w-6xl mx-auto py-32 px-8 text-center"> <div className="max-w-3xl mx-auto"> <h2 className="text-foreground-secondary text-sm font-medium uppercase tracking-wider mb-6"> - Works With Your Codebase + {t('eyebrow')} </h2> <p className="text-foreground-primary text-2xl md:text-5xl leading-[1.1] font-light mb-8 text-balance"> - Connect Your Existing Project. Start Designing in Minutes. + {t('title')} </p> <p className="text-foreground-secondary text-lg max-w-xl mx-auto text-balance"> - No rebuilding. No migration. Connect your React, Next.js, or Vue project and design with your real components — the ones your engineers already built. + {t('description')} </p> </div> </div> ); }Per coding guideline:
apps/web/client/src/app/**/*.tsxmust avoid hardcoded user-facing text and usenext-intlmessages/hooks instead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/_components/landing-page/builder-features-intro-section.tsx` around lines 8 - 15, Extract the three hardcoded strings in the BuilderFeaturesIntroSection component into next-intl message keys under the landing.builderFeaturesIntroSection namespace (e.g., title, subtitle, description) in your English messages JSON and other locales, then import and call useTranslations("landing.builderFeaturesIntroSection") inside the component and replace the literal strings with t('title'), t('subtitle'), and t('description'); ensure keys match the JSON entries and the component continues to render the same markup.apps/web/client/src/app/features/page.tsx-19-39 (1)
19-39:⚠️ Potential issue | 🟠 MajorLocalize newly added user-facing copy instead of hardcoding it
The new FAQ entries, AI summary copy, bullet list, and CTA text are hardcoded strings. Please move these into
next-intlmessages and read them via translation hooks.Suggested pattern
+import { useTranslations } from 'next-intl'; export default function FeaturesPage() { + const t = useTranslations('featuresPage'); + const featuresFaqs = [ + { question: t('faqs.whatIs.question'), answer: t('faqs.whatIs.answer') }, + // ... + ]; return ( <WebsiteLayout showFooter={true}> - <h1>Onlook Features: Design with Your Real Components</h1> + <h1>{t('summary.title')}</h1> {/* ... */} - <CTASection ctaText={`Ready to stop rebuilding?`} /> + <CTASection ctaText={t('cta.text')} /> </WebsiteLayout> ); }As per coding guidelines,
apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.Also applies to: 49-66, 78-79
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/features/page.tsx` around lines 19 - 39, The FAQ entries and other user-facing strings in the page component are hardcoded; extract each visible string (FAQ objects' "question" and "answer" values, the AI summary text, bullet list items, and CTA text) into next-intl message keys, then replace the literals with translation lookups using the next-intl hook (e.g., useTranslations) inside the page component so the FAQ array builds its values from t('...') calls rather than raw strings; ensure message keys are descriptive (e.g., faq.questionX, faq.answerX, ai.summary, cta.text) and update any references that consume the FAQ array or CTA to use the translated values.apps/web/client/src/app/features/page.tsx-47-67 (1)
47-67:⚠️ Potential issue | 🟠 MajorAvoid using
sr-onlycontent as crawler-targeted copyThis section is labeled for AI/crawlers but exposed to screen readers, which can create noisy/duplicative narration for assistive-tech users. Prefer visible summary content or rely on structured data/meta for crawler signals instead of hidden long-form text.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/features/page.tsx` around lines 47 - 67, The visually hidden AI-friendly summary section (the <section> with className "sr-only" and aria-label "Features Summary") shouldn't contain long crawler-targeted copy because it will be read by screen readers and produce duplicate / noisy narration; instead remove the "sr-only" hiding, move this summary into visible content or into structured metadata (e.g., page meta tags or JSON-LD) and/or condense it for assistive users, and update the component rendering in page.tsx to either render a visible summary block or inject the same text into head/meta while keeping the accessible section concise.apps/web/client/src/app/_components/landing-page/feature-blocks/direct-editing.tsx-13-15 (1)
13-15:⚠️ Potential issue | 🟠 MajorLocalize the new “Canvas Manipulation” copy.
These newly added user-facing strings should come from
next-intlmessages rather than inline literals. As per coding guidelinesapps/web/client/src/app/**/*.tsx: “Avoid hardcoded user-facing text; use next-intl messages/hooks”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/_components/landing-page/feature-blocks/direct-editing.tsx` around lines 13 - 15, Replace the hardcoded user-facing text in direct-editing.tsx with next-intl translations: import useTranslations from 'next-intl', call const t = useTranslations('FeatureBlocks') (or the appropriate namespace), replace the span content "Canvas Manipulation" with t('canvasManipulation.title') and the paragraph "Drag, resize, and arrange elements..." with t('canvasManipulation.description'); add the corresponding keys to the locale messages JSON so strings are loaded at runtime.apps/web/client/src/app/_components/landing-page/feature-blocks/ai-chat-preview-block.tsx-12-14 (1)
12-14:⚠️ Potential issue | 🟠 MajorLocalize the new AI chat block copy.
Please move these added strings into
next-intlmessage keys/hooks instead of inline JSX text. As per coding guidelinesapps/web/client/src/app/**/*.tsx: “Avoid hardcoded user-facing text; use next-intl messages/hooks”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/_components/landing-page/feature-blocks/ai-chat-preview-block.tsx` around lines 12 - 14, The two hardcoded strings in the Ai chat preview component should be pulled into next-intl message keys and referenced via the useTranslations hook instead of inline JSX; in the component (ai-chat-preview-block.tsx) import and call useTranslations with the appropriate namespace (e.g., const t = useTranslations('Landing')), replace the span content "AI That Understands Context" with t('aiChat.title') and the paragraph content with t('aiChat.description'), and add these keys (aiChat.title and aiChat.description) to your locale JSON message files for each locale.apps/web/client/src/app/_components/landing-page/feature-blocks/components.tsx-261-264 (1)
261-264:⚠️ Potential issue | 🟠 MajorUse localized message keys for this updated components copy.
The new title/description are hardcoded user-facing text and should be moved to
next-intl. As per coding guidelinesapps/web/client/src/app/**/*.tsx: “Avoid hardcoded user-facing text; use next-intl messages/hooks”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/_components/landing-page/feature-blocks/components.tsx` around lines 261 - 264, Replace the two hardcoded user-facing strings in the JSX (the span containing "Your Real Components" and the p containing "Design with the buttons, cards, and layouts your engineers already built. Your actual design system.") with localized message lookups using next-intl: import and call useTranslations (e.g., const t = useTranslations('featureBlocks')) at the top of the component, replace the span text with t('yourRealComponents.title') and the p text with t('yourRealComponents.description'), and add corresponding keys ("featureBlocks.yourRealComponents.title" and "featureBlocks.yourRealComponents.description") to the relevant next-intl messages JSON for supported locales.apps/web/client/src/app/_components/hero/index.tsx-76-78 (1)
76-78:⚠️ Potential issue | 🟠 MajorUpdate hero tagline to use
next-intlkeys.These new hero strings are hardcoded and should be sourced from localization messages. As per coding guidelines
apps/web/client/src/app/**/*.tsx: “Avoid hardcoded user-facing text; use next-intl messages/hooks”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/_components/hero/index.tsx` around lines 76 - 78, The hero component currently contains hardcoded strings ("Design with your real components.", "Ship PRs, not prototypes."); replace these with next-intl message lookups by importing and using the useTranslations hook (e.g., const t = useTranslations('hero')) inside the Hero component and swap the literal JSX text for t('tagline.line1') and t('tagline.line2') (or your chosen message keys) so the strings come from localization messages; ensure the message keys are added to the relevant locale JSON and that the hook is invoked in the same component (index.tsx) where the JSX currently renders the two lines.apps/web/client/src/app/features/prototype/layout.tsx-4-139 (1)
4-139:⚠️ Potential issue | 🟠 MajorMove new page copy/metadata strings into
next-intlmessages.This introduces hardcoded user-facing text (metadata, keyword copy, and FAQ answers) instead of message-based localization. As per coding guidelines
apps/web/client/src/app/**/*.tsx: “Avoid hardcoded user-facing text; use next-intl messages/hooks”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/features/prototype/layout.tsx` around lines 4 - 139, The file embeds hardcoded user-facing strings (metadata title/description/keywords, openGraph/twitter fields, and the jsonLd and faqJsonLd text blobs) — move these into next-intl message files and replace the inline literals with lookups; extract each string/keyword array and each FAQ question/answer into the i18n messages (e.g., keys like prototype.title, prototype.description, prototype.keywords, prototype.faq.q1, prototype.faq.a1), then in the layout replace the hardcoded values in the metadata object, openGraph, twitter, jsonLd, and faqJsonLd constants with calls to the next-intl API you already use in the app (getMessages or useTranslations / getTranslations in server components) to load localized text and arrays so metadata and JSON‑LD are generated from messages instead of literals.apps/web/client/src/app/_components/landing-page/what-can-onlook-do-section.tsx-311-334 (1)
311-334:⚠️ Potential issue | 🟠 MajorExternalized copy is required for these updated feature cards.
The new card headings/descriptions are hardcoded literals; please move them to
next-intlmessages and reference via hooks. As per coding guidelinesapps/web/client/src/app/**/*.tsx: “Avoid hardcoded user-facing text; use next-intl messages/hooks”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/_components/landing-page/what-can-onlook-do-section.tsx` around lines 311 - 334, In the what-can-onlook-do-section component, replace hardcoded card headings and descriptions with next-intl message lookups: import and use the next-intl hook (e.g., useTranslations) in the component, create message keys for each literal (e.g., "worksWithYourCodebase.title"/".description", "builtForTeams.title"/".description", "directGithubIntegration.title"/".description", "shipPRsNotPrototypes.title"/".description", "powerUserShortcuts.title"/".description", "referenceAnythingInChat.title"/".description"), move the literal strings into the locale JSON files, and call t('key') where the current JSX uses the hardcoded text (the divs containing those headings and descriptions); leave dynamic bits like getKeyboardShortcut() and isShortcutAnimating unchanged but wrap surrounding copy with translated strings where needed.apps/web/client/src/app/features/ai/page.tsx-47-66 (1)
47-66:⚠️ Potential issue | 🟠 MajorThe new AI summary section should use localized messages.
This block introduces substantial user-facing copy directly in JSX; please move it into
next-intlresources/hooks. As per coding guidelinesapps/web/client/src/app/**/*.tsx: “Avoid hardcoded user-facing text; use next-intl messages/hooks”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/features/ai/page.tsx` around lines 47 - 66, The AI summary section currently contains hardcoded user-facing text inside the JSX (the <section aria-label="AI Features Summary"> block and its children: the title, paragraph, h2, and list items); extract each visible string into next-intl message keys (e.g. ai.summary.title, ai.summary.description, ai.summary.heading, ai.summary.feature.*), add them to the locale resource files, and replace the literals in the page component with translations via useTranslations (or the project's next-intl hook) so the section renders t('ai.summary.title'), t('ai.summary.description'), t('ai.summary.heading'), and t('ai.summary.feature.xxx') instead of hardcoded strings while preserving the existing structure and aria-label.apps/web/client/src/app/features/ai-for-frontend/layout.tsx-4-185 (1)
4-185:⚠️ Potential issue | 🟠 MajorLocalize the newly added public copy via
next-intlinstead of inline literals.The added metadata and FAQ JSON-LD content is hardcoded in this TSX file. As per coding guidelines
apps/web/client/src/app/**/*.tsx: “Avoid hardcoded user-facing text; use next-intl messages/hooks”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/features/ai-for-frontend/layout.tsx` around lines 4 - 185, The metadata, jsonLd, and faqJsonLd objects (symbols: metadata, jsonLd, faqJsonLd) contain hardcoded user-facing text — replace those inline literals with next-intl message lookups (e.g., useTranslations or the next-intl message loader) and move all strings into locale message files; update the layout component to call the i18n hook (useTranslations) and substitute message keys for titles, descriptions, keywords, openGraph/twitter fields, FAQ question/answer texts, featureList and aggregateRating fields so the file reads values from the locale messages instead of hardcoded strings.apps/web/client/src/app/_components/landing-page/ai-features-intro-section.tsx-8-14 (1)
8-14:⚠️ Potential issue | 🟠 MajorExternalize this intro copy to translation messages.
These updated strings are user-facing and currently hardcoded in JSX.
As per coding guidelines:
apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/_components/landing-page/ai-features-intro-section.tsx` around lines 8 - 14, The three hardcoded user-facing strings in ai-features-intro-section.tsx should be moved into next-intl translation messages and referenced via the next-intl hook (e.g., useTranslations) in the AiFeaturesIntroSection component; create message keys like "aiFeatures.title", "aiFeatures.lead", and "aiFeatures.desc" in the locale JSON, load them with useTranslations("aiFeatures") and replace the JSX literals with t("title"), t("lead"), and t("desc") respectively so the component no longer contains hardcoded copy.apps/web/client/src/app/site-map/layout.tsx-4-30 (1)
4-30:⚠️ Potential issue | 🟠 MajorLocalize sitemap metadata copy instead of hardcoding strings.
title,description, and OpenGraph text are user-facing metadata and should follow the same translation flow as page content.As per coding guidelines:
apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/site-map/layout.tsx` around lines 4 - 30, The sitemap layout currently hardcodes user-facing metadata (the title, description, openGraph fields and the jsonLd const); replace these literal strings by pulling localized messages via next-intl (e.g., call useTranslations or the appropriate message loader in this layout component) and use the translation keys when constructing the exported metadata object (title, description, openGraph.title, openGraph.description) and the jsonLd.name/jsonLd.description values; ensure corresponding message keys are added to the locale JSON files and preserve URLs/structure while only swapping text values for translated strings.apps/web/client/src/app/_components/landing-page/faq-section.tsx-20-53 (1)
20-53:⚠️ Potential issue | 🟠 MajorLocalize the updated FAQ entries instead of embedding raw copy.
The newly added FAQ questions/answers are hardcoded, which breaks the i18n standard for app components.
As per coding guidelines:
apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/_components/landing-page/faq-section.tsx` around lines 20 - 53, defaultFaqs contains hardcoded user-facing strings; replace them with localized messages using next-intl. Import and use the useTranslations (or messages) hook in the component that consumes defaultFaqs, convert defaultFaqs into a factory/function (e.g., getDefaultFaqs or buildDefaultFaqs) that calls t('faq.<key>.question') and t('faq.<key>.answer') for each entry (use descriptive keys like faq.whatIsOnlook.question and faq.whatIsOnlook.answer), and update the consumer to call that factory so all question/answer text comes from next-intl message keys rather than raw literals.apps/web/client/src/app/_components/landing-page/feature-blocks/brand-compliance.tsx-138-141 (1)
138-141:⚠️ Potential issue | 🟠 MajorLocalize the updated block title and description.
The new text is user-facing copy introduced as hardcoded literals.
Based on learnings: Applies to
apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/_components/landing-page/feature-blocks/brand-compliance.tsx` around lines 138 - 141, Replace the hardcoded user-facing strings in the BrandCompliance component (the span with "Design System Guardrails" and the paragraph "AI is constrained to your colors, fonts, and tokens. No drift. No off-brand outputs.") with next-intl lookups: import and call useTranslations with an appropriate namespace, reference message keys (e.g. "designSystemGuardrails.title" and "designSystemGuardrails.description"), and replace the literal text with t('...') so the UI uses localized messages; add those keys to the corresponding messages JSON for the component's locale files.apps/web/client/src/app/_components/hero/claude-code-hero.tsx-26-80 (1)
26-80:⚠️ Potential issue | 🟠 MajorMove hero labels/copy to
next-intlinstead of hardcoding.The newly added workflow badge, headline, subtitle, CTA label, and footer labels are all user-facing literals.
As per coding guidelines:
apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/_components/hero/claude-code-hero.tsx` around lines 26 - 80, Replace hardcoded user-facing strings in the Claude Code hero component (the motion.h1 "Workflows", motion.p "Claude Code for Designers", motion.h2 subtitle, CTA label "Book a Demo" inside Button/anchor, and footer spans "GitHub stars", "YC W25", "Open Source") with next-intl message lookups; import and use useTranslations (or intl.formatMessage) to read keys (e.g. hero.title, hero.tagline, hero.subtitle, hero.cta, hero.footer.stars, hero.footer.yc, hero.footer.openSource), add those keys to the locale message JSON, and pass formatted strings into the JSX where the literals currently appear so no user-facing text remains hardcoded.apps/web/client/src/app/features/builder/page.tsx-47-68 (1)
47-68:⚠️ Potential issue | 🟠 MajorMove the new summary copy into
next-intlmessages.This block introduces screen-reader-exposed user text directly in JSX, which bypasses localization and creates copy drift risk across locales.
As per coding guidelines:
apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/features/builder/page.tsx` around lines 47 - 68, The screen-reader-only summary JSX (the <section> with className "sr-only" and aria-label "Visual Builder Summary" containing the h1, p, h2 and list items) contains hardcoded user-facing copy; extract all visible strings into next-intl messages and replace them with translation lookups (e.g., useTranslations or t('...') calls) in the page component (page.tsx). Create corresponding keys in your locale message files (e.g., builder.* keys in the locale JSON for each language), import and call useTranslations('builder') at the top of the page component, and replace each hardcoded string (h1 text, paragraph, h2 and each <li>) with t('key') lookups so the SR-only block reads localized messages instead of inline text. Ensure aria-label is also localized via messages.apps/web/client/src/app/site-map/layout.tsx-77-80 (1)
77-80:⚠️ Potential issue | 🟠 MajorAdd biome-ignore comment and escape
<in JSON-LD script to satisfy default Biome security rule.The
noDangerouslySetInnerHtmlrule is part of Biome's recommended ruleset and is enabled by default in this project. Line 79 violates it without an exception. This pattern occurs across 15+ files in the codebase (app/layout.tsx, app/about/layout.tsx, app/features/, app/workflows/, app/faq/page.tsx, and others).Suggested patch
+ {/* biome-ignore lint/security/noDangerouslySetInnerHtml: JSON-LD requires raw script content; payload is static and `<` is escaped. */} <script type="application/ld+json" - dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} + dangerouslySetInnerHTML={{ + __html: JSON.stringify(jsonLd).replace(/</g, '\\u003c'), + }} />This fix should be applied systematically across all affected layout files.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/site-map/layout.tsx` around lines 77 - 80, The script tag using dangerouslySetInnerHTML in layout.tsx violates Biome's noDangerouslySetInnerHtml rule; add a file-local Biome ignore comment (// biome-ignore noDangerouslySetInnerHtml) immediately above the JSX element and ensure the JSON-LD string escapes any "<" characters by replacing them with their safe escape sequence (e.g., use JSON.stringify(jsonLd).replace(/</g, '\\u003c')) before passing it to dangerouslySetInnerHTML so the script content is safe; update the usage in the component where jsonLd and the <script ... dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} /> JSX appear.apps/web/client/src/app/workflows/page.tsx-30-52 (1)
30-52:⚠️ Potential issue | 🟠 MajorExternalize newly added workflows copy to translations.
The new page content is hardcoded throughout (cards, summary, hero copy, CTA text).
As per coding guidelines
apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.Also applies to: 59-113, 167-169
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/workflows/page.tsx` around lines 30 - 52, The workflows array currently hardcodes user-facing strings (the title and description fields for items like the workflows constant and any hero/CTA text used elsewhere between lines 59-113 and 167-169); replace these literals with translation keys and load them via next-intl (use useTranslations or the messages object) so each item uses t('workflows.claudeCode.title'), t('workflows.claudeCode.description'), etc., and update any usages of Routes.WORKFLOWS_CLAUDE_CODE, Icons.Sparkles, and the Codex item to reference the translated strings; add the corresponding keys to the locale message files and ensure the component imports and calls useTranslations at the top to populate the workflows array from translations instead of hardcoded text.apps/web/client/src/app/features/ai/layout.tsx-4-63 (1)
4-63:⚠️ Potential issue | 🟠 MajorExternalize metadata/FAQ copy to
next-intlrather than hardcoding.Title/description/keyword and FAQ strings are directly embedded in this layout.
As per coding guidelines
apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.Also applies to: 75-132
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/features/ai/layout.tsx` around lines 4 - 63, The metadata and copy in the AI layout (variables title, description, keywords array, openGraph.title/openGraph.description, twitter.title/twitter.description, alternates.canonical, and any FAQ strings in the same file) are hardcoded; replace them with localized message lookups via next-intl (e.g., loadMessages/useTranslations or the app-level next-intl hook used in the repo) and reference message keys instead of raw strings, then pass those localized strings into the metadata objects (openGraph, twitter, alternates) and any FAQ rendering; ensure all new message keys are added to the locale JSON files and remove the hardcoded literals so the file uses intl lookups throughout.apps/web/client/src/app/workflows/vibe-coding/page.tsx-17-42 (1)
17-42:⚠️ Potential issue | 🟠 MajorLocalize new vibe-coding copy via
next-intlinstead of hardcoded strings.The page introduces extensive user-facing content directly in code.
As per coding guidelines
apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.Also applies to: 74-409
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/workflows/vibe-coding/page.tsx` around lines 17 - 42, The vibeCodingFaqs array contains hardcoded user-facing strings; extract all question/answer texts into next-intl message keys (e.g., vibeCoding.faq.0.question / vibeCoding.faq.0.answer or named keys) in your locale JSON files, import/use the next-intl hook (useTranslations) in the component that declares vibeCodingFaqs, and replace each literal with t('…') lookups (or construct the array using t for each faq entry); ensure messages are added for all locales and update any typing/loader usage so the page uses localized strings instead of the hardcoded vibeCodingFaqs values.apps/web/client/src/app/_components/landing-page/benefits-section.tsx-16-19 (1)
16-19:⚠️ Potential issue | 🟠 MajorMove revised benefits copy into
next-intlmessages.These newly added strings are hardcoded in JSX, which blocks localization and drifts from the app i18n pattern.
As per coding guidelines
apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.Also applies to: 30-33, 44-47
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/_components/landing-page/benefits-section.tsx` around lines 16 - 19, The hardcoded user-facing strings in the BenefitsSection JSX (the h2 "AI That Understands Context", the p "AI Constrained to Your Design System", and the longer descriptive paragraph) must be moved into next-intl message files and referenced via the next-intl hook; add keys like benefits.title, benefits.subtitle, benefits.description to your locale messages, import and call useTranslations (e.g., const t = useTranslations('BenefitsSection') or appropriate namespace) in the BenefitsSection component, and replace the literal strings with t('benefits.title'), t('benefits.subtitle'), and t('benefits.description'); apply the same extraction for the other hardcoded strings noted around lines 30-33 and 44-47 so all user-facing text follows the app i18n pattern.apps/web/client/src/app/site-map/page.tsx-23-175 (1)
23-175:⚠️ Potential issue | 🟠 MajorLocalize sitemap titles/descriptions instead of hardcoding them.
The newly added section titles, descriptions, and page copy are embedded directly in code rather than sourced from translation messages.
As per coding guidelines
apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.Also applies to: 243-276, 282-295
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/site-map/page.tsx` around lines 23 - 175, The sitemap entries currently hardcode all user-facing strings (section titles, link titles and descriptions) — replace them with next-intl translations: import useTranslations (or the project's i18n hook) at the top of page.tsx, call const t = useTranslations('sitemap'), and replace every literal title/description/href label in the sitemap objects (e.g., the objects with anchor "main", "features", "workflows", "resources", "social", "legal" and their link entries like "Home", "Pricing", "All Features", "AI", etc.) with t(...) keys (for example t('main.title'), t('main.links.home.title'), t('main.links.home.description') or a similar nested key convention you choose), keeping non-text fields (href, external) intact; add corresponding keys to the locale message files. Ensure all places flagged in the diff are converted.apps/web/client/src/app/features/ai-for-frontend/page.tsx-15-52 (1)
15-52:⚠️ Potential issue | 🟠 MajorMove new feature-page content into
next-intlmessages.The added FAQs, summaries, section copy, and CTA text are hardcoded.
As per coding guidelines
apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.Also applies to: 79-104, 113-291
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/features/ai-for-frontend/page.tsx` around lines 15 - 52, The aiFrontendFaqs array and other hardcoded user-facing strings in page.tsx must be moved into next-intl message files and referenced via the next-intl hooks; create keys for each FAQ question/answer (e.g. aiFrontend.faqs.0.question, aiFrontend.faqs.0.answer) and for summaries, section copy, and CTA text, add them to the locale JSON used by next-intl, then update page.tsx to import/use useTranslations (or the appropriate next-intl helper) and replace direct string literals in the aiFrontendFaqs definition and other hardcoded strings (search for aiFrontendFaqs plus the other referenced blocks ~lines 79-104 and 113-291) to pull values from t('aiFrontend.faqs.N.question') / t('aiFrontend.faqs.N.answer') and t('aiFrontend.sectionKey') instead of inline text.apps/web/client/src/app/workflows/claude-code/page.tsx-15-40 (1)
15-40:⚠️ Potential issue | 🟠 MajorMove newly added workflow copy/FAQ text to
next-intlkeys.This page introduces extensive hardcoded user-facing content.
As per coding guidelines
apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks.Also applies to: 63-90, 104-111, 117-134, 150-248
apps/web/client/src/app/faq/page.tsx-179-182 (1)
179-182:⚠️ Potential issue | 🟠 MajorReplace JSON-LD injection with non-dangerous script content.
Using
dangerouslySetInnerHTMLhere is avoidable and currently conflicts with security linting.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/faq/page.tsx` around lines 179 - 182, The current script tag uses dangerouslySetInnerHTML to inject JSON-LD; instead call generateFAQJsonLd(), stringify its result into a local variable and render it as the script's children so React outputs plain text (e.g. const faqJson = JSON.stringify(generateFAQJsonLd()); then <script type="application/ld+json">{faqJson}</script>). Update the script in page.tsx that references generateFAQJsonLd to use this child-string approach to avoid dangerouslySetInnerHTML and satisfy the security linter.
🟡 Minor comments (7)
apps/web/client/src/app/not-found.tsx-32-35 (1)
32-35:⚠️ Potential issue | 🟡 MinorHardcoded user-facing text violates i18n guidelines.
The page contains multiple hardcoded strings that should use
next-intlmessages/hooks for internationalization:
- Line 32:
"404"- Lines 33-35:
"Seems like you ventured somewhere unknown on your journey. Let us help you find your way."- Line 49:
"Back to home"As per coding guidelines: "Avoid hardcoded user-facing text; use next-intl messages/hooks instead."
🌐 Proposed fix using next-intl
'use client'; import Link from 'next/link'; import { motion } from 'framer-motion'; +import { useTranslations } from 'next-intl'; import { WebsiteLayout } from './_components/website-layout'; import { Illustrations } from './_components/landing-page/illustrations'; export default function NotFound() { + const t = useTranslations('NotFound'); return ( <WebsiteLayout> <main className="relative min-h-screen w-full overflow-hidden"> ... <motion.div className="space-y-3" ... > - <h1 className="text-6xl font-light tracking-tight text-foreground-primary">404</h1> - <p className="text-xl text-foreground-tertiary"> - Seems like you ventured somewhere unknown on your journey. Let us help you find your way. - </p> + <h1 className="text-6xl font-light tracking-tight text-foreground-primary">{t('title')}</h1> + <p className="text-xl text-foreground-tertiary"> + {t('description')} + </p> </motion.div> ... <Link href="/" className="..." > - Back to home + {t('backToHome')} </Link>Add corresponding messages to your locale files:
{ "NotFound": { "title": "404", "description": "Seems like you ventured somewhere unknown on your journey. Let us help you find your way.", "backToHome": "Back to home" } }Also applies to: 49-49
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/not-found.tsx` around lines 32 - 35, Replace hardcoded user-facing strings in the NotFound component with next-intl message lookups: import and call useTranslations('NotFound') (or the app-wide translator) inside the component, then replace the h1 content "404" with t('title'), the paragraph text with t('description'), and the "Back to home" link/button text with t('backToHome'); ensure corresponding keys are added to your locale JSON (NotFound.title, NotFound.description, NotFound.backToHome).apps/web/client/src/app/features/prototype/layout.tsx-144-151 (1)
144-151:⚠️ Potential issue | 🟡 MinorThe
dangerouslySetInnerHTMLpattern for JSON-LD is flagged by Biome but is the recommended Next.js approach.This file uses Biome's recommended linter rules, which include
noDangerouslySetInnerHtml. However, for JSON-LD in the app router, Next.js documentation recommends this exact pattern. You have two options:
- Add a narrowly scoped lint exception with a comment explaining why this pattern is necessary for JSON-LD serialization:
// biome-ignore lint/security/noDangerouslySetInnerHtml: JSON-LD structured data requires dangerouslySetInnerHTML per Next.js guidelines <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd).replace(/</g, "\\u003c") }} />
- Escape angle brackets in the JSON output to reduce XSS risk (shown above as
.replace(/</g, "\\u003c")).The
next/scriptcomponent with children would not serialize JSON-LD correctly;dangerouslySetInnerHTMLis the required approach for this use case.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/features/prototype/layout.tsx` around lines 144 - 151, The two script tags that set JSON-LD via dangerouslySetInnerHTML (using jsonLd and faqJsonLd) trigger Biome's noDangerouslySetInnerHtml rule but are the correct Next.js pattern; update each script to both escape angle brackets in the JSON (e.g., JSON.stringify(...).replace(/</g, "\\u003c")) and add a narrow biome-ignore comment immediately above each script (e.g., // biome-ignore lint/security/noDangerouslySetInnerHtml: JSON-LD requires dangerouslySetInnerHTML per Next.js) so the linter is documented and the output is safer.apps/web/client/src/app/_components/landing-page/faq-dropdown.tsx-25-29 (1)
25-29:⚠️ Potential issue | 🟡 MinorSet an explicit button type on the FAQ toggle.
Without
type="button", this can submit a surrounding form unexpectedly.Suggested patch
<button + type="button" className="flex items-center justify-between w-full text-left text-foreground-primary text-lg focus:outline-none cursor-pointer py-2" onClick={onToggle} aria-expanded={isOpen} >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/_components/landing-page/faq-dropdown.tsx` around lines 25 - 29, The FAQ toggle button in the FAQ dropdown lacks an explicit type which can cause it to submit a surrounding form; update the button element in the FAQ component (where onToggle and aria-expanded={isOpen} are used) to include type="button" so it behaves as a plain toggle rather than a submit control.apps/web/client/src/app/_components/landing-page/faq-dropdown.tsx-50-52 (1)
50-52:⚠️ Potential issue | 🟡 MinorRender
faq.answerin a neutral container instead of<p>.
answeracceptsReact.ReactNode; wrapping it in<p>can generate invalid nested markup for richer nodes.Suggested patch
<div ref={contentRef}> - <p className="text-foreground-secondary text-regular leading-relaxed">{faq.answer}</p> + <div className="text-foreground-secondary text-regular leading-relaxed">{faq.answer}</div> </div>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/_components/landing-page/faq-dropdown.tsx` around lines 50 - 52, The FAQ item renders faq.answer (a React.ReactNode) inside a <p>, which can produce invalid nested markup for richer nodes; in the LandingPage FAQ component (faq-dropdown.tsx) replace the <p className="text-foreground-secondary text-regular leading-relaxed">{faq.answer}</p> with a neutral container (e.g., <div> or a React.Fragment) that preserves the styling classes (apply the classes to the neutral container or wrap children appropriately) while keeping the existing contentRef and outer structure unchanged so rich nodes inside faq.answer render valid HTML.apps/web/client/src/app/faq/layout.tsx-20-31 (1)
20-31:⚠️ Potential issue | 🟡 MinorMissing OpenGraph and Twitter images.
Other layout files in this PR (features/builder, workflows, about, etc.) include
imagesin theiropenGraphand🔧 Proposed fix to add images
openGraph: { title: 'FAQ | Onlook', description: 'Everything you need to know about Onlook - the AI-powered visual editor for frontend development.', type: 'website', url: 'https://onlook.com/faq', siteName: 'Onlook', + images: [ + { + url: 'https://framerusercontent.com/images/ScnnNT7JpmUya7afqGAets8.png', + }, + ], }, twitter: { card: 'summary_large_image', title: 'FAQ | Onlook', description: 'Everything you need to know about Onlook - the AI-powered visual editor for frontend development.', + images: ['https://framerusercontent.com/images/ScnnNT7JpmUya7afqGAets8.png'], },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/faq/layout.tsx` around lines 20 - 31, The FAQ layout's metadata is missing social preview images: update the openGraph and twitter objects in layout.tsx to include the same image(s) used by the other layouts (add an images array to openGraph and the corresponding images property for twitter) so social shares show a preview; modify the openGraph and twitter entries in this file to match the image keys/URLs used by features/builder, workflows, and about.apps/web/client/src/app/workflows/page.tsx-66-72 (1)
66-72:⚠️ Potential issue | 🟡 MinorKeep sr-only integration items consistent with rendered workflow cards.
The hidden summary includes “Cursor,” but no corresponding card exists in
workflows.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/workflows/page.tsx` around lines 66 - 72, The sr-only "Available Integrations" list in page.tsx is out of sync with the actual rendered workflows (it mentions "Cursor" but there's no corresponding workflow card); either remove "Cursor" from the hidden list or add a matching workflow entry so the accessible summary matches the UI. Locate the hardcoded ul in page.tsx (the "Available Integrations" list) and update its items to mirror the source of truth used to render cards (the workflows collection or the component that maps over workflows) so the sr-only items and the rendered workflow cards remain consistent.apps/web/client/src/app/site-map/page.tsx-211-231 (1)
211-231:⚠️ Potential issue | 🟡 MinorStop re-binding the scroll listener on each section change.
The effect depends on
currentSection, so it repeatedly removes/re-adds the listener during scroll updates.💡 Suggested fix
- useEffect(() => { + useEffect(() => { const handleScroll = () => { const offset = 120; let activeIdx = 0; for (let i = 0; i < sectionRefs.current.length; i++) { const ref = sectionRefs.current[i]; if (ref) { const top = ref.getBoundingClientRect().top; if (top <= offset) { activeIdx = i; } } } - if (sitemapSections[activeIdx]?.anchor && sitemapSections[activeIdx]?.anchor !== currentSection) { - setCurrentSection(sitemapSections[activeIdx]?.anchor || ''); - } + const next = sitemapSections[activeIdx]?.anchor || ''; + setCurrentSection(prev => (prev === next ? prev : next)); }; window.addEventListener('scroll', handleScroll, { passive: true }); handleScroll(); return () => window.removeEventListener('scroll', handleScroll); - }, [currentSection]); + }, []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/site-map/page.tsx` around lines 211 - 231, The useEffect is re-registering the scroll listener whenever currentSection changes; move the dependency off currentSection so the listener is added once: define handleScroll (which reads sectionRefs.current, sitemapSections and calls setCurrentSection) inside the effect but use an empty dependency array (or include only stable refs/props), or alternatively wrap handleScroll in useCallback with stable deps and call window.addEventListener in a useEffect with [], ensuring cleanup removes the same function; update the effect that currently references useEffect, handleScroll, sectionRefs, sitemapSections, currentSection, and setCurrentSection accordingly.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added responsive mobile preview of the Onlook interface mockup to "The Solution" sections on both /workflows/claude-code and /workflows/vibe-coding pages. Uses absolute positioning to show a cropped view of the editor from the right side, matching the approach used on the /features page. - Container height: 880px to prevent clipping the prototype - Mockup positioned with right-10 offset for visual balance - Hidden on desktop (md:hidden), desktop version unchanged Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
apps/web/client/src/app/workflows/vibe-coding/page.tsx (1)
10-16: Switch relative imports to configured path aliases.These imports should use
@/*(or~/*) aliases instead of../../...to match repo standards.Suggested diff
-import { UnicornBackground } from '../../_components/hero/unicorn-background'; -import { CTASection } from '../../_components/landing-page/cta-section'; -import { FAQSection } from '../../_components/landing-page/faq-section'; -import { OnlookInterfaceMockup } from '../../_components/landing-page/onlook-interface-mockup'; -import { useGitHubStats } from '../../_components/top-bar/github'; -import { WebsiteLayout } from '../../_components/website-layout'; +import { UnicornBackground } from '@/app/_components/hero/unicorn-background'; +import { CTASection } from '@/app/_components/landing-page/cta-section'; +import { FAQSection } from '@/app/_components/landing-page/faq-section'; +import { OnlookInterfaceMockup } from '@/app/_components/landing-page/onlook-interface-mockup'; +import { useGitHubStats } from '@/app/_components/top-bar/github'; +import { WebsiteLayout } from '@/app/_components/website-layout';As per coding guidelines, "
apps/web/client/src/**/*.{ts,tsx}: Use path aliases@/* and ~/* for imports that map to apps/web/client/src/*".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/client/src/app/workflows/vibe-coding/page.tsx` around lines 10 - 16, Replace the relative imports in this file with the project path aliases: change the imports for UnicornBackground, CTASection, FAQSection, OnlookInterfaceMockup, useGitHubStats and WebsiteLayout to use the configured alias (e.g. "@/..." or "~/*") that maps to apps/web/client/src, so each import uses the alias root instead of ../../..; update each import statement accordingly and ensure your tsconfig/webpack alias matches if needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/client/src/app/workflows/vibe-coding/page.tsx`:
- Around line 17-419: The page contains hardcoded user-facing strings (e.g.,
vibeCodingFaqs array, text inside VibeCodingHero, headings/paragraphs in
VibeCodingWorkflowPage, CTASection props and FAQSection call) which must be
moved to next-intl messages; replace each literal with a call to the i18n
message hook (useTranslations or t) and reference message keys (e.g.,
"vibeCoding.hero.title", "vibeCoding.faq.0.question", "vibeCoding.cta.text") and
pass translated strings into FAQSection and CTASection instead of raw literals;
extract the vibeCodingFaqs data into a localized factory that reads from
messages and ensure VibeCodingHero uses translations for starCount label and all
<motion> text nodes.
---
Nitpick comments:
In `@apps/web/client/src/app/workflows/vibe-coding/page.tsx`:
- Around line 10-16: Replace the relative imports in this file with the project
path aliases: change the imports for UnicornBackground, CTASection, FAQSection,
OnlookInterfaceMockup, useGitHubStats and WebsiteLayout to use the configured
alias (e.g. "@/..." or "~/*") that maps to apps/web/client/src, so each import
uses the alias root instead of ../../..; update each import statement
accordingly and ensure your tsconfig/webpack alias matches if needed.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/web/client/src/app/workflows/claude-code/page.tsxapps/web/client/src/app/workflows/vibe-coding/page.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/client/src/app/workflows/claude-code/page.tsx
| const vibeCodingFaqs = [ | ||
| { | ||
| question: 'What is vibe coding?', | ||
| answer: "Vibe coding is a workflow where you describe what you want in natural language and AI generates the code. Most vibe coding tools are designed for solo use, not teams.", | ||
| }, | ||
| { | ||
| question: 'What is the problem with vibe coding today?', | ||
| answer: "Most vibe coding tools are solo workflows. You can't easily share work-in-progress, collaborate in real-time, or hand off to engineers. The output is often throwaway code that doesn't match your design system.", | ||
| }, | ||
| { | ||
| question: 'How does Onlook make vibe coding work for teams?', | ||
| answer: 'Onlook adds a visual canvas layer. Share your canvas with teammates, leave spatial comments, work together in real-time. AI is constrained to your design system, so outputs are consistent. Changes become PRs engineers can merge.', | ||
| }, | ||
| { | ||
| question: 'Can I use my existing vibe coding tools with Onlook?', | ||
| answer: 'Yes. Use any AI coding tool to build. Open in Onlook to visually iterate, collaborate with your team, and refine before shipping. Onlook works with your existing codebase.', | ||
| }, | ||
| { | ||
| question: 'Does vibe coding in Onlook use my real components?', | ||
| answer: "Yes. Unlike tools that generate generic HTML, Onlook connects to your component library. AI uses YOUR buttons, cards, and layouts. No brand drift, no rebuilding.", | ||
| }, | ||
| { | ||
| question: 'How do I share vibe-coded work with my team?', | ||
| answer: 'Share your canvas link. Teammates can view, comment spatially, and collaborate in real-time. When ready, submit changes as a PR for engineers to review.', | ||
| }, | ||
| ]; | ||
|
|
||
| // Helper function for blur animations | ||
| const getBlurAnimationProps = (delay: number = 0) => ({ | ||
| initial: { opacity: 0, filter: 'blur(4px)' }, | ||
| whileInView: { opacity: 1, filter: 'blur(0px)' }, | ||
| viewport: { once: true, margin: '-100px 0px -100px 0px', amount: 0.3 }, | ||
| transition: { | ||
| duration: 0.6, | ||
| delay, | ||
| ease: [0.25, 0.46, 0.45, 0.94] as const, | ||
| }, | ||
| style: { | ||
| willChange: 'opacity, filter', | ||
| transform: 'translateZ(0)', | ||
| }, | ||
| }); | ||
|
|
||
| function VibeCodingHero() { | ||
| const { formatted: starCount } = useGitHubStats(); | ||
|
|
||
| return ( | ||
| <div className="relative flex h-full w-full flex-col items-center justify-center gap-12 p-8 text-center text-lg"> | ||
| <UnicornBackground /> | ||
| <div className="relative z-20 flex max-w-3xl flex-col items-center gap-6 pt-4 pb-2"> | ||
| <motion.h1 | ||
| className="text-foreground-secondary mb-4 text-sm font-medium tracking-wider uppercase" | ||
| initial={{ opacity: 0, filter: 'blur(4px)' }} | ||
| animate={{ opacity: 1, filter: 'blur(0px)' }} | ||
| transition={{ duration: 0.6, ease: 'easeOut' }} | ||
| style={{ willChange: 'opacity, filter', transform: 'translateZ(0)' }} | ||
| > | ||
| Vibe Coding for Teams | ||
| </motion.h1> | ||
| <motion.p | ||
| className="text-center text-4xl !leading-[1] leading-tight font-light text-balance md:text-6xl" | ||
| initial={{ opacity: 0, filter: 'blur(4px)' }} | ||
| animate={{ opacity: 1, filter: 'blur(0px)' }} | ||
| transition={{ duration: 0.6, delay: 0.1, ease: 'easeOut' }} | ||
| style={{ willChange: 'opacity, filter', transform: 'translateZ(0)' }} | ||
| > | ||
| Vibe coding has a collaboration problem | ||
| </motion.p> | ||
| <motion.p | ||
| className="text-foreground-secondary mx-auto max-w-xl text-center text-lg" | ||
| initial={{ opacity: 0, filter: 'blur(4px)' }} | ||
| animate={{ opacity: 1, filter: 'blur(0px)' }} | ||
| transition={{ duration: 0.6, delay: 0.15, ease: 'easeOut' }} | ||
| style={{ willChange: 'opacity, filter', transform: 'translateZ(0)' }} | ||
| > | ||
| Most AI coding tools are solo workflows. Onlook adds the missing layer — | ||
| a visual canvas where teams collaborate on AI-generated UIs with their real components. | ||
| </motion.p> | ||
| <motion.div | ||
| className="mt-8" | ||
| initial={{ opacity: 0, filter: 'blur(4px)' }} | ||
| animate={{ opacity: 1, filter: 'blur(0px)' }} | ||
| transition={{ duration: 0.6, delay: 0.3, ease: 'easeOut' }} | ||
| style={{ willChange: 'opacity, filter', transform: 'translateZ(0)' }} | ||
| > | ||
| <Button | ||
| asChild | ||
| variant="secondary" | ||
| size="lg" | ||
| className="hover:bg-foreground-primary hover:text-background-primary cursor-pointer p-6 transition-all duration-300" | ||
| > | ||
| <a href={ExternalRoutes.BOOK_DEMO} target="_blank" rel="noopener noreferrer"> | ||
| Book a Demo | ||
| </a> | ||
| </Button> | ||
| </motion.div> | ||
| <motion.div | ||
| className="text-foreground-secondary mt-8 flex items-center justify-center gap-6 text-sm" | ||
| initial={{ opacity: 0, filter: 'blur(4px)' }} | ||
| animate={{ opacity: 1, filter: 'blur(0px)' }} | ||
| transition={{ duration: 0.6, delay: 0.4, ease: 'easeOut' }} | ||
| style={{ willChange: 'opacity, filter', transform: 'translateZ(0)' }} | ||
| > | ||
| <div className="flex items-center gap-2"> | ||
| <span>{starCount}+ GitHub stars</span> | ||
| </div> | ||
| <div className="bg-foreground-secondary h-1 w-1 rounded-full"></div> | ||
| <div className="flex items-center gap-2"> | ||
| <span>YC W25</span> | ||
| </div> | ||
| <div className="bg-foreground-secondary h-1 w-1 rounded-full"></div> | ||
| <div className="flex items-center gap-2"> | ||
| <span>Open Source</span> | ||
| </div> | ||
| </motion.div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export default function VibeCodingWorkflowPage() { | ||
| return ( | ||
| <CreateManagerProvider> | ||
| <WebsiteLayout showFooter={true}> | ||
| {/* AI-Friendly Summary Section */} | ||
| <section className="sr-only" aria-label="Vibe Coding Workflow Summary"> | ||
| <h1>Vibe Coding for Teams: Add Collaboration to Your AI Coding Workflow</h1> | ||
| <p> | ||
| Vibe coding — describing what you want and letting AI generate the code — is powerful but has | ||
| a collaboration problem. Most AI coding tools are solo workflows. You can't | ||
| easily share work-in-progress, collaborate in real-time, or ensure outputs match your design | ||
| system. Onlook solves this. | ||
| </p> | ||
| <h2>The Problem with Solo Vibe Coding</h2> | ||
| <ul> | ||
| <li>Solo workflow — hard to share work-in-progress with teammates</li> | ||
| <li>Throwaway code — doesn't use your real components</li> | ||
| <li>Brand drift — AI generates generic HTML/CSS, not your design system</li> | ||
| <li>No handoff path — "now how do I share this?" becomes a blocker</li> | ||
| </ul> | ||
| <h2>Onlook Makes Vibe Coding Work for Teams</h2> | ||
| <ul> | ||
| <li>Visual canvas — see and arrange AI-generated UIs spatially</li> | ||
| <li>Your real components — AI constrained to your design system</li> | ||
| <li>Real-time collaboration — share canvas, leave spatial comments</li> | ||
| <li>PR output — changes become mergeable pull requests</li> | ||
| <li>Works with any AI coding tool you already use</li> | ||
| </ul> | ||
| </section> | ||
|
|
||
| {/* Hero Section */} | ||
| <div className="flex h-screen w-screen items-center justify-center" id="hero"> | ||
| <VibeCodingHero /> | ||
| </div> | ||
|
|
||
| {/* The Problem Section */} | ||
| <section className="w-full bg-black py-32"> | ||
| <div className="mx-auto max-w-6xl px-8"> | ||
| <motion.h2 | ||
| className="text-foreground-secondary mb-6 text-sm font-medium uppercase tracking-wider" | ||
| {...getBlurAnimationProps()} | ||
| > | ||
| The Problem | ||
| </motion.h2> | ||
| <motion.p | ||
| className="mb-16 max-w-3xl text-4xl font-light leading-tight text-balance md:text-5xl" | ||
| {...getBlurAnimationProps(0.1)} | ||
| > | ||
| Solo vibe coding doesn't scale. Teams need to share, iterate, and ship together. | ||
| </motion.p> | ||
|
|
||
| <div className="grid gap-8 md:grid-cols-2 lg:grid-cols-4"> | ||
| {[ | ||
| { | ||
| icon: Icons.Person, | ||
| title: 'Solo workflow', | ||
| description: 'Most AI coding tools are designed for individual use. Sharing means screenshots or screen shares.', | ||
| }, | ||
| { | ||
| icon: Icons.Trash, | ||
| title: 'Throwaway code', | ||
| description: "AI generates generic HTML/CSS. You'll rebuild it anyway to use your real components.", | ||
| }, | ||
| { | ||
| icon: Icons.Brand, | ||
| title: 'Brand drift', | ||
| description: "Every vibe-coded UI looks different. AI doesn't know your design system.", | ||
| }, | ||
| { | ||
| icon: Icons.QuestionMarkCircled, | ||
| title: '"Now what?"', | ||
| description: 'You built something cool. Now how do you share it, get feedback, or hand it off?', | ||
| }, | ||
| ].map((item, index) => ( | ||
| <motion.div | ||
| key={item.title} | ||
| className="flex flex-col gap-4" | ||
| {...getBlurAnimationProps(0.2 + index * 0.1)} | ||
| > | ||
| <item.icon className="text-foreground-secondary h-5 w-5" /> | ||
| <h3 className="text-base font-medium text-balance">{item.title}</h3> | ||
| <p className="text-foreground-secondary text-base text-balance">{item.description}</p> | ||
| </motion.div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| </section> | ||
|
|
||
| {/* The Solution Section */} | ||
| <section className="w-full bg-black pt-32 pb-16"> | ||
| <div className="mx-auto max-w-6xl px-8"> | ||
| <motion.h2 | ||
| className="text-foreground-secondary mb-6 text-sm font-medium uppercase tracking-wider" | ||
| {...getBlurAnimationProps()} | ||
| > | ||
| The Solution | ||
| </motion.h2> | ||
| <motion.p | ||
| className="mb-24 max-w-3xl text-4xl font-light leading-tight text-balance md:text-5xl" | ||
| {...getBlurAnimationProps(0.1)} | ||
| > | ||
| Onlook adds the visual layer. Vibe code with your team, on your components, to real PRs. | ||
| </motion.p> | ||
| </div> | ||
|
|
||
| {/* Editor Mockup - Desktop */} | ||
| <motion.div | ||
| className="hidden md:block w-screen h-[44rem] items-center justify-center mb-24" | ||
| {...getBlurAnimationProps(0.2)} | ||
| > | ||
| <OnlookInterfaceMockup /> | ||
| </motion.div> | ||
|
|
||
| {/* Editor Mockup - Mobile */} | ||
| <motion.div | ||
| className="md:hidden w-screen relative overflow-hidden h-[500px]" | ||
| {...getBlurAnimationProps(0.2)} | ||
| > | ||
| <div className="absolute top-0 right-10 h-[800px] w-[1000px]"> | ||
| <OnlookInterfaceMockup /> | ||
| </div> | ||
| </motion.div> | ||
|
|
||
| <div className="mx-auto max-w-6xl px-8"> | ||
| <div className="grid gap-8 md:grid-cols-4"> | ||
| {[ | ||
| { | ||
| icon: Icons.Layers, | ||
| title: 'Visual canvas', | ||
| description: 'See and arrange AI-generated UIs spatially. Not just terminal output.', | ||
| }, | ||
| { | ||
| icon: Icons.Component, | ||
| title: 'Your real components', | ||
| description: 'AI uses your buttons, cards, layouts. Not generic HTML.', | ||
| }, | ||
| { | ||
| icon: Icons.Person, | ||
| title: 'Team collaboration', | ||
| description: 'Share canvas, leave spatial comments, work together in real-time.', | ||
| }, | ||
| { | ||
| icon: Icons.Branch, | ||
| title: 'Ship PRs', | ||
| description: 'Changes become pull requests. Engineers review and merge.', | ||
| }, | ||
| ].map((item, index) => ( | ||
| <motion.div | ||
| key={item.title} | ||
| className="flex flex-col gap-3" | ||
| {...getBlurAnimationProps(0.3 + index * 0.1)} | ||
| > | ||
| <item.icon className="text-foreground-secondary h-5 w-5" /> | ||
| <h3 className="text-base font-medium text-balance">{item.title}</h3> | ||
| <p className="text-foreground-secondary text-sm text-balance">{item.description}</p> | ||
| </motion.div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| </section> | ||
|
|
||
| {/* How It Works Section */} | ||
| <section className="w-full bg-black py-32"> | ||
| <div className="mx-auto max-w-6xl px-8"> | ||
| <motion.h2 | ||
| className="text-foreground-secondary mb-6 text-sm font-medium uppercase tracking-wider" | ||
| {...getBlurAnimationProps()} | ||
| > | ||
| How It Works | ||
| </motion.h2> | ||
| <motion.p | ||
| className="mb-16 max-w-3xl text-4xl font-light leading-tight text-balance md:text-5xl" | ||
| {...getBlurAnimationProps(0.1)} | ||
| > | ||
| Vibe code anywhere. Collaborate in Onlook. Ship together. | ||
| </motion.p> | ||
|
|
||
| <div className="grid gap-12 md:grid-cols-3"> | ||
| {[ | ||
| { | ||
| step: '01', | ||
| title: 'Build with any AI tool', | ||
| description: 'Use any AI coding tool to generate your initial UI. Onlook works with your existing codebase.', | ||
| }, | ||
| { | ||
| step: '02', | ||
| title: 'Iterate on the canvas', | ||
| description: 'Open in Onlook to visually refine. Drag elements, adjust styles, use AI with your real components. See changes in real code.', | ||
| }, | ||
| { | ||
| step: '03', | ||
| title: 'Collaborate and ship', | ||
| description: 'Share your canvas with teammates. Leave spatial comments. When ready, submit as a PR for engineers to review and merge.', | ||
| }, | ||
| ].map((item, index) => ( | ||
| <motion.div | ||
| key={item.step} | ||
| className="flex flex-col gap-4" | ||
| {...getBlurAnimationProps(0.2 + index * 0.1)} | ||
| > | ||
| <span className="text-foreground-tertiary text-sm font-medium">{item.step}</span> | ||
| <h3 className="text-xl font-medium">{item.title}</h3> | ||
| <p className="text-foreground-secondary text-base text-balance">{item.description}</p> | ||
| </motion.div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| </section> | ||
|
|
||
| {/* Comparison Section */} | ||
| <section className="w-full bg-black py-32"> | ||
| <div className="mx-auto max-w-6xl px-8"> | ||
| <motion.h2 | ||
| className="text-foreground-secondary mb-6 text-sm font-medium uppercase tracking-wider" | ||
| {...getBlurAnimationProps()} | ||
| > | ||
| Vibe Coding: Solo vs. Team | ||
| </motion.h2> | ||
| <motion.p | ||
| className="mb-16 max-w-3xl text-4xl font-light leading-tight text-balance md:text-5xl" | ||
| {...getBlurAnimationProps(0.1)} | ||
| > | ||
| The difference between prototyping alone and shipping with your team. | ||
| </motion.p> | ||
|
|
||
| <motion.div | ||
| className="grid gap-8 md:grid-cols-2" | ||
| {...getBlurAnimationProps(0.2)} | ||
| > | ||
| {/* Solo Column */} | ||
| <div className="border-foreground-primary/10 rounded-lg border p-8"> | ||
| <h3 className="text-foreground-tertiary mb-6 text-sm font-medium uppercase tracking-wider"> | ||
| Solo Vibe Coding | ||
| </h3> | ||
| <ul className="space-y-4"> | ||
| {[ | ||
| 'Generate generic HTML/CSS', | ||
| 'Share via screenshots', | ||
| 'Rebuild to use your components', | ||
| 'Manual handoff process', | ||
| 'Code needs translation', | ||
| ].map((item) => ( | ||
| <li key={item} className="text-foreground-secondary flex items-start gap-3"> | ||
| <Icons.CrossCircled className="text-foreground-tertiary mt-0.5 h-5 w-5 flex-shrink-0" /> | ||
| <span>{item}</span> | ||
| </li> | ||
| ))} | ||
| </ul> | ||
| </div> | ||
|
|
||
| {/* Team Column */} | ||
| <div className="border-foreground-primary/30 rounded-lg border bg-gradient-to-b from-white/5 to-transparent p-8"> | ||
| <h3 className="text-foreground-secondary mb-6 text-sm font-medium uppercase tracking-wider"> | ||
| Team Vibe Coding with Onlook | ||
| </h3> | ||
| <ul className="space-y-4"> | ||
| {[ | ||
| 'Use your real components', | ||
| 'Share a live canvas', | ||
| 'AI constrained to your design system', | ||
| 'Collaborate with spatial comments', | ||
| 'Ship PRs engineers can merge', | ||
| ].map((item) => ( | ||
| <li key={item} className="text-foreground-primary flex items-start gap-3"> | ||
| <Icons.CheckCircled className="text-teal-500 mt-0.5 h-5 w-5 flex-shrink-0" /> | ||
| <span>{item}</span> | ||
| </li> | ||
| ))} | ||
| </ul> | ||
| </div> | ||
| </motion.div> | ||
| </div> | ||
| </section> | ||
|
|
||
| {/* FAQ Section */} | ||
| <FAQSection faqs={vibeCodingFaqs} title="Frequently asked questions" /> | ||
|
|
||
| {/* CTA Section */} | ||
| <CTASection | ||
| ctaText={`Ready to vibe code\nwith your team?`} | ||
| buttonText="Book a Demo" | ||
| href={ExternalRoutes.BOOK_DEMO} | ||
| /> |
There was a problem hiding this comment.
Move user-facing copy to next-intl messages instead of hardcoding strings.
This page embeds large amounts of display copy directly in TSX (FAQ content, headings, paragraph text, labels, CTA copy), which breaks the repo i18n rule and makes localization/error-proof copy updates harder.
Suggested direction (partial)
+import { useTranslations } from 'next-intl';
-const vibeCodingFaqs = [
- { question: 'What is vibe coding?', answer: '...' },
- ...
-];
export default function VibeCodingWorkflowPage() {
+ const t = useTranslations('workflows.vibeCoding');
+ const vibeCodingFaqs = [
+ { question: t('faq.items.whatIs.question'), answer: t('faq.items.whatIs.answer') },
+ // ...
+ ];
return (
...
- <h1>Vibe Coding for Teams: Add Collaboration to Your AI Coding Workflow</h1>
+ <h1>{t('summary.title')}</h1>
...
- <FAQSection faqs={vibeCodingFaqs} title="Frequently asked questions" />
+ <FAQSection faqs={vibeCodingFaqs} title={t('faq.title')} />
...
- <CTASection ctaText={`Ready to vibe code\nwith your team?`} buttonText="Book a Demo" ... />
+ <CTASection ctaText={t('cta.text')} buttonText={t('cta.button')} ... />As per coding guidelines, "apps/web/client/src/app/**/*.tsx: Avoid hardcoded user-facing text; use next-intl messages/hooks".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/client/src/app/workflows/vibe-coding/page.tsx` around lines 17 -
419, The page contains hardcoded user-facing strings (e.g., vibeCodingFaqs
array, text inside VibeCodingHero, headings/paragraphs in
VibeCodingWorkflowPage, CTASection props and FAQSection call) which must be
moved to next-intl messages; replace each literal with a call to the i18n
message hook (useTranslations or t) and reference message keys (e.g.,
"vibeCoding.hero.title", "vibeCoding.faq.0.question", "vibeCoding.cta.text") and
pass translated strings into FAQSection and CTASection instead of raw literals;
extract the vibeCodingFaqs data into a localized factory that reads from
messages and ensure VibeCodingHero uses translations for starCount label and all
<motion> text nodes.
Summary
Major website refresh focused on AI-native discoverability, updated copy based on GTM research, and new workflow pages. This PR implements the copy revisions from
COPY_REVISIONS_V1_5.mdand makes all public pages optimized for AI agent discovery.What's Changed
New Pages Created
/workflows- Hub page for AI tool integrations (Claude Code, Vibe Coding, Codex)/workflows/claude-code- Dedicated page for Claude Code + Onlook workflow with FAQ, comparison table, and use cases/workflows/vibe-coding- Team collaboration page addressing the "vibe coding has a collaboration problem" messaging/site-map- Visual HTML sitemap with sticky sidebar navigation (FAQ-style layout)/sitemap.ts- Dynamic XML sitemap generator for SEO/features/ai-for-frontend- AI for frontend development feature page/features/ai- AI features overview page/features/builder- Visual builder features page/features/prototype- Prototyping features pageAI-Native Optimizations
Every public page now includes:
sr-onlysummary sections for AI crawlersPages updated with AI-native metadata:
/about- Organization schema with founders, team size/faq- FAQPage schema/featuresand all sub-pages/workflowsand all sub-pages/site-mapCopy Revisions (from GTM research)
Homepage "...and so much more" section:
Features page refresh:
Feature block spacing fix:
mb-6marginsgap-6across all feature blocks404 Page Redesign
Other Updates
Files Changed
Test Plan
/workflows,/workflows/claude-code,/workflows/vibe-coding,/site-map)/sitemap.xml🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
UI / Content
Documentation / SEO
Navigation
Bug Fixes