Skip to content

Comments

Lingo-launch@pr1#2001

Open
mesusheel01 wants to merge 2 commits intolingodotdev:mainfrom
mesusheel01:malevolant-shrine
Open

Lingo-launch@pr1#2001
mesusheel01 wants to merge 2 commits intolingodotdev:mainfrom
mesusheel01:malevolant-shrine

Conversation

@mesusheel01
Copy link

@mesusheel01 mesusheel01 commented Feb 20, 2026

Summary

Introduces LingoLaunch — a multilingual landing page builder with authentication, dashboard management, and dynamic language switching.

Changes

  • Added user authentication (register/login)
  • Built dashboard for creating and managing pages
  • Implemented modular page editor (hero, features, text, CTA, testimonial sections)
  • Added real-time language switching with translation synchronization
  • Implemented dark/light theme toggle
  • Added page preview functionality
  • Added project documentation and configuration setup

Testing

  • Verified authentication flow (register/login/logout)
  • Tested language switching across all sections
  • Confirmed page creation, editing, and persistence
  • Checked theme toggle state
  • Manual testing completed locally

No automated tests added in this PR.

Visuals

Demo video attached below.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 20, 2026

📝 Walkthrough

Walkthrough

Introduces a new "LingoLaunch" community project—a complete Next.js-based multilingual page builder. Includes authentication, dashboard for page management, editor for content creation with sections, preview mode, locale switching with automatic translation sync, theme support, localStorage-backed persistence, and localization files for English, Spanish, German, French, and Hindi.

Changes

Cohort / File(s) Summary
Configuration & Tooling
.gitignore, package.json, tsconfig.json, next.config.ts, postcss.config.mjs, eslint.config.mjs
Project setup with Next.js, TypeScript, Tailwind, Lingo.dev integration, ESLint rules, and dependency declarations.
Documentation
README.md
Comprehensive project overview, features, tech stack, workflow, running instructions, and future improvements.
Core Storage Layer
app/lib/storage.ts
Browser-side persistence layer with full CRUD for users, pages, and sections; authentication (login, register, logout); utilities for aggregating content strings and managing section ordering.
Layout & Providers
app/layout.tsx, app/components/Providers.tsx, theme/ThemeProvider.tsx
Root layout with metadata, Providers wrapper composing LingoProvider, LocaleSync, and ThemeProvider; theme persistence and locale hydration from localStorage with validation.
Navigation & Core Components
app/components/Navbar.tsx, app/components/LanguageSwitcher.tsx, app/components/SectionRenderer.tsx
Navbar with user actions and theme toggle; LanguageSwitcher with optional translation sync via API; SectionRenderer for rendering hero, features, text, CTA, and testimonial sections with Lingo context integration.
API Routes
app/api/lingo-sync/route.ts
Server-side POST endpoint that accepts translation texts, generates a dynamic React source file, executes npx lingo run, and returns status.
Page Components
app/page.tsx, app/login/page.tsx, app/register/page.tsx, app/dashboard/page.tsx, app/editor/[pageId]/page.tsx, app/preview/[pageId]/page.tsx
Marketing homepage, authentication pages with form validation, dashboard for page management (CRUD), editor with section editing and reordering, and read-only preview mode.
Styling & Auto-generated
app/globals.css, app/lingo-dynamic-source.tsx
Global CSS with Tailwind integration, dark mode theme tokens, and custom properties; auto-generated component exposing translation strings via Lingo hook.
Localization & Configuration
i18n.json, locales/*.json
i18n schema defining source (en) and target (es, de, fr, hi) locales; translation files for hero and CTA strings in five languages.

Sequence Diagrams

sequenceDiagram
    participant User
    participant LanguageSwitcher
    participant Storage
    participant API as /api/lingo-sync
    participant CMD as lingo run (CLI)
    
    User->>LanguageSwitcher: Select Language
    LanguageSwitcher->>Storage: Fetch all content strings
    LanguageSwitcher->>Storage: Get new locale
    Storage-->>LanguageSwitcher: Return all content
    LanguageSwitcher->>API: POST texts array
    API->>API: Generate lingo-dynamic-source.tsx
    API->>CMD: Execute npx lingo run
    CMD-->>API: Translation output
    API-->>LanguageSwitcher: Success response
    LanguageSwitcher->>Storage: Update locale in localStorage
    LanguageSwitcher-->>User: Render in new language
Loading
sequenceDiagram
    participant User
    participant Editor as EditorPage
    participant Storage
    participant Lingo as Storage API
    
    User->>Editor: Load page by ID
    Editor->>Storage: getPage(pageId)
    Storage-->>Editor: Return SitePage with sections
    User->>Editor: Add new section
    Editor->>Lingo: addSection(pageId, type)
    Lingo-->>Storage: Create section with default content
    Storage-->>Editor: Return updated page
    User->>Editor: Edit section content
    Editor->>Lingo: updateSection(pageId, sectionId, content)
    Lingo-->>Storage: Update section content
    User->>Editor: Reorder sections
    Editor->>Lingo: moveSectionUp/Down(pageId, sectionId)
    Lingo-->>Storage: Persist new order
    Storage-->>Editor: Refresh page data
    Editor-->>User: Display updated page
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

community

Suggested reviewers

  • sumitsaurabh927
  • vrcprl

Poem

🐰 A builder is born, with languages five,
Lingo's pages now bloom and thrive,
Sections dance, locales switch with cheer,
Multilingual dreams brought crystal clear! 🌍✨

🚥 Pre-merge checks | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The PR description provides a clear summary and comprehensive list of changes, but lacks required sections from the template: no business logic tests are documented, visuals section is incomplete, and the checklist items are unchecked. Add specific business logic test descriptions, attach visual evidence (screenshots/video), and verify/check all checklist items including changeset status and breaking changes documentation.
Title check ❓ Inconclusive The title 'Lingo-launch@pr1' is vague and does not clearly describe the main changes in the pull request, which introduces a complete multilingual page builder application with numerous components, pages, and configuration files. Replace with a descriptive title that summarizes the primary change, such as 'Add LingoLaunch multilingual page builder application' or 'Introduce LingoLaunch community project with i18n support'.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (12)
community/lingo-launch/app/lingo-dynamic-source.tsx-14-14 (1)

14-14: ⚠️ Potential issue | 🟡 Minor

Demo content contains informal, unprofessional text.

Line 14: "Here are the prices for using this application its 1000 rs one time payment" — this reads like placeholder/test content with grammatical issues. If this ships as the default template, replace with polished copy or clearly mark it as sample data.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/lingo-dynamic-source.tsx` at line 14, The demo
copy inside the JSX call to t(...) is informal and grammatically incorrect;
replace the string passed to t (the translation key or default text used in
lingo-dynamic-source.tsx) with polished, professional copy or mark it explicitly
as sample/demo text (e.g., "Sample pricing: one-time payment of ₹1,000") and, if
using i18n keys, add a proper translation key and entries instead of inline
placeholder text to ensure production users don't see the test sentence.
community/lingo-launch/next.config.ts-9-9 (1)

9-9: ⚠️ Potential issue | 🟡 Minor

Hindi (hi) locale is missing from targetLocales.

The i18n.json declares hi as a target locale, locale files exist for it, but it is not listed in targetLocales. Add "hi" to align the configuration:

Suggested change
-    targetLocales: ["es", "de", "fr"],
+    targetLocales: ["es", "de", "fr", "hi"],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/next.config.ts` at line 9, The i18n targetLocales
array is missing the Hindi locale; update the targetLocales configuration in
next.config.ts to include "hi" so it matches i18n.json and the existing locale
files (edit the targetLocales array where it currently contains ["es", "de",
"fr"] to also include "hi").
community/lingo-launch/app/lib/storage.ts-42-44 (1)

42-44: ⚠️ Potential issue | 🟡 Minor

saveUsers and savePages lack the isBrowser() guard present in their corresponding read functions.

getUsers() (line 37) and getAllPages() (line 79) check isBrowser() before accessing localStorage, but saveUsers and savePages do not. If these are ever invoked in an SSR context (e.g., during Next.js pre-rendering), they'll throw a ReferenceError on localStorage.

🛡️ Add guards
 function saveUsers(users: User[]) {
+  if (!isBrowser()) return;
   localStorage.setItem('lingolaunch_users', JSON.stringify(users));
 }
 function savePages(pages: SitePage[]) {
+  if (!isBrowser()) return;
   localStorage.setItem('lingolaunch_pages', JSON.stringify(pages));
 }

Also applies to: 101-103

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/lib/storage.ts` around lines 42 - 44, The
saveUsers and savePages functions access localStorage without checking for a
browser environment; wrap their bodies with the same isBrowser() guard used by
getUsers and getAllPages (e.g., return early if !isBrowser()) so they never call
localStorage in SSR, and ensure any callers relying on their return behavior
still work (no-op or return false/undefined as appropriate); update saveUsers
and savePages (the functions referenced alongside getUsers and getAllPages) to
match the defensive pattern used by the read functions.
community/lingo-launch/locales/fr.json-1-5 (1)

1-5: ⚠️ Potential issue | 🟡 Minor

Minor inconsistency: "lingo" is lowercase here and in es.json, but capitalized as "Lingo" in de.json.

Line 3 uses "lingo" (lowercase), while de.json uses "Lingo" (capitalized). Consider aligning the brand name casing across all locale files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/locales/fr.json` around lines 1 - 5, The brand name
casing is inconsistent in the French locale: change the value for
"hero.subtitle" (and any other occurrences of "lingo") to use the same
capitalization as other locales (e.g., "Lingo") so the brand reads consistently
across translations; locate the "hero.subtitle" key in fr.json and update the
string to replace "lingo" with "Lingo".
community/lingo-launch/app/editor/[pageId]/page.tsx-91-94 (1)

91-94: ⚠️ Potential issue | 🟡 Minor

No confirmation before removing a section — users can accidentally delete content.

handleRemove immediately deletes the section without prompting. A simple window.confirm() would prevent accidental data loss.

🛡️ Suggested fix
   const handleRemove = () => {
+    if (!window.confirm('Are you sure you want to remove this section?')) return;
     removeSection(pageId, section.id);
     onUpdate();
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/editor/`[pageId]/page.tsx around lines 91 - 94,
The handleRemove function currently calls removeSection(pageId, section.id) and
onUpdate() immediately, risking accidental deletion; update handleRemove to
prompt the user (e.g., via window.confirm with a clear message) and only call
removeSection(pageId, section.id) and onUpdate() if the user confirms, otherwise
do nothing; keep the confirmation logic inside handleRemove so callers don't
need to change.
community/lingo-launch/app/register/page.tsx-62-110 (1)

62-110: ⚠️ Potential issue | 🟡 Minor

Same missing htmlFor/id pairing on all form labels as in LoginPage.

The name, email, and password <label> elements all lack htmlFor, and the inputs they describe have no id. Add matching htmlFor/id pairs ("name", "email", "password") to all three field pairs, following the same fix pattern noted in LoginPage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/register/page.tsx` around lines 62 - 110, Add
matching htmlFor/id attributes for the three form field pairs in
register/page.tsx: update the Name label and its input to use htmlFor="name" and
id="name", the Email label and input to use htmlFor="email" and id="email", and
the Password label and input to use htmlFor="password" and id="password" (the
inputs bound via setName, setEmail, setPassword and using the User, Mail, Lock
icons should receive the ids).
community/lingo-launch/README.md-27-47 (1)

27-47: ⚠️ Potential issue | 🟡 Minor

README describes a different implementation than what was actually built — several references are stale.

The following items in the documented project structure and tech stack do not match the actual code in this PR:

README says Actual code
"Node.js File System API" (tech stack, line 27) localStorage only — no FS API
api/save-and-compile/ (structure, line 40) app/api/lingo-sync/route.ts
public/locales/ (structure, line 43) locales/[locale].json (top-level, not under public/)
lingo.config.ts (structure, line 45) i18n.json
public/locales/{pageId}/en.json (line 78) locales/[locale].json

Additionally, the "Install Lingo CLI" step (line 145) runs pnpm dlx lingo compile, which invokes the compiler — it doesn't install the CLI.

Please update the README to reflect the actual implementation so it doesn't mislead contributors.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/README.md` around lines 27 - 47, README is stale and
references files/commands that don't exist; update the docs to match the actual
implementation: replace "Node.js File System API" with "localStorage" (or note
both if applicable), change api/save-and-compile/ to
app/api/lingo-sync/route.ts, update public/locales/ and
public/locales/{pageId}/en.json to locales/[locale].json at repo root, swap
lingo.config.ts references to i18n.json, and fix the "Install Lingo CLI" step to
clarify that `pnpm dlx lingo compile` runs the compiler (not installs the CLI)
or provide the correct install command; ensure all file/path examples and the
tech stack section reference these exact symbols (localStorage,
app/api/lingo-sync/route.ts, locales/[locale].json, i18n.json, `pnpm dlx lingo
compile`) so the README accurately reflects the code.
community/lingo-launch/app/login/page.tsx-55-68 (1)

55-68: ⚠️ Potential issue | 🟡 Minor

Labels are not programmatically associated with their inputs — accessibility failure.

All <label> elements on this form (and on the register form) lack htmlFor, and their corresponding <input> elements lack id. Because the label is a sibling of the input rather than a parent, the browser cannot associate them, meaning screen readers will not announce the label when the input is focused.

♿ Proposed fix
-<label className="block text-sm font-medium text-card-foreground mb-1.5">
+<label htmlFor="email" className="block text-sm font-medium text-card-foreground mb-1.5">
   Email
 </label>
 <div className="relative">
   <Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
   <input
+    id="email"
     type="email"
     required
     ...

Apply the same pattern (pair htmlFor="password" with id="password") to the password field.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/login/page.tsx` around lines 55 - 68, The label
for the email input is not programmatically associated with the input which
breaks accessibility; add a unique id to the email input (e.g., id="email") and
set the label's htmlFor to that id, and do the same for the password field
(e.g., id="password" with htmlFor="password") so screen readers can link labels
to inputs; update the JSX around the email input (value={email}, onChange={e =>
setEmail(e.target.value)}, and the corresponding password input/handler) to
include the id attributes and matching htmlFor on their <label> elements.
community/lingo-launch/app/register/page.tsx-17-35 (1)

17-35: ⚠️ Potential issue | 🟡 Minor

setLoading never reset to false on the success path.

On a successful registration (user is truthy), router.push("/dashboard") is called but setLoading(false) is not. If navigation is delayed (e.g. slow client-side transition, user navigating back), the submit button stays permanently disabled. The same gap exists in LoginPage.

🔧 Proposed fix
 const user = register(name, email, password);
 if (user) {
+  setLoading(false);
   router.push("/dashboard");
 } else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/register/page.tsx` around lines 17 - 35, The
submit handler handleSubmit sets loading=true but never resets it on success, so
the button can remain disabled; update handleSubmit (and the analogous LoginPage
handler) to ensure setLoading(false) is called on the success path — e.g., call
setLoading(false) just before calling router.push("/dashboard") or wrap the
register + navigation flow in a try/finally that calls setLoading(false) in
finally; reference the handleSubmit function, the register(...) call, and
router.push(...) when making the change.
community/lingo-launch/app/globals.css-2-4 (1)

2-4: ⚠️ Potential issue | 🟡 Minor

Stylelint false positives for Tailwind v4 at-rules — configure the ignore list.

@variant and @theme are valid Tailwind CSS v4 at-rules, but the project's stylelint config uses scss/at-rule-no-unknown, which has no built-in awareness of them and flags them as errors. This will fail any lint CI step. Suppress the false positives by adding the Tailwind v4 at-rules to the ignore list in your Stylelint config:

// .stylelintrc or equivalent
{
  "rules": {
+   "scss/at-rule-no-unknown": [true, {
+     "ignoreAtRules": ["theme", "variant", "utility", "source"]
+   }]
  }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/globals.css` around lines 2 - 4, Stylelint is
flagging Tailwind v4 at-rules (`@variant` and `@theme`) as unknown via the
scss/at-rule-no-unknown rule; update the Stylelint config to add these Tailwind
at-rules to the ignore list so they are not treated as errors. Modify your
stylelint config (where scss/at-rule-no-unknown is configured) to include an
ignoreAtRules array containing "variant" and "theme" (i.e., add "variant" and
"theme" to scss/at-rule-no-unknown's ignoreAtRules) so the CSS in globals.css
using `@variant` and `@theme` passes linting.
community/lingo-launch/README.md-33-100 (1)

33-100: ⚠️ Potential issue | 🟡 Minor

Add language identifiers to fenced code blocks.

Seven code blocks (lines 33, 57, 63, 77, 83, 89, 100) are missing language specifiers, triggering MD040 lint warnings from markdownlint-cli2. Use text or the appropriate language tag where a specific syntax isn't needed.

📝 Example fix for the directory-tree block and URL blocks
-```
+```text
 lingo-launch/
 ...
-```
+```text
 /editor/{pageId}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/README.md` around lines 33 - 100, Several fenced code
blocks in README.md (the directory tree block showing "lingo-launch/" and the
example path/code blocks like "/editor/{pageId}", "/editor/landing", the
generated file paths "public/locales/{pageId}/en.json", the commands "lingo
compile", and the preview path) lack language specifiers and cause MD040 lint
warnings; fix by adding a language tag (use text for plain examples) to each
fenced block so they become e.g. ```text ... ```—update the blocks around the
directory-tree and all example path/command blocks mentioned in the diff.
community/lingo-launch/app/page.tsx-176-204 (1)

176-204: ⚠️ Potential issue | 🟡 Minor

Add Hindi to the supported languages showcase on the marketing page.

Hindi is configured as a supported target language in i18n.json with a corresponding locales/hi.json file, but the language list on lines 186-191 includes only EN, ES, DE, FR. Add Hindi to the showcase or clarify why it's excluded.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/page.tsx` around lines 176 - 204, The language
showcase array in the JSX section (the inline array mapped over to render
language cards) is missing Hindi; update that array used in the map callback
(the object list with elements like { code: "EN", name: "English" }) to include
Hindi (e.g., { code: "HI", name: "हिन्दी" } or "Hindi") so the Hindi locale
configured in i18n.json/locales/hi.json is displayed, or alternatively add a
comment in that same array explaining why Hindi is intentionally excluded.
🧹 Nitpick comments (12)
community/lingo-launch/next.config.ts (1)

5-14: Consider naming the exported function for better debuggability.

An anonymous default export makes stack traces harder to read.

Proposed fix
-export default async function (): Promise<NextConfig> {
+export default async function createNextConfig(): Promise<NextConfig> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/next.config.ts` around lines 5 - 14, The exported
async function is anonymous which hurts stack traces; give it a descriptive name
(for example getNextConfig or createNextConfig) while keeping it as the default
export so stack traces show that function name; locate the anonymous default
export function in next.config.ts (the exported async function returning
withLingo(nextConfig, {...})) and change its declaration to a named function
(e.g., export default async function getNextConfig(): Promise<NextConfig>) so
the symbol appears in error traces and logs.
community/lingo-launch/app/lingo-dynamic-source.tsx (1)

1-53: Auto-generated file is committed to version control.

This file is generated by the /api/lingo-sync route and marked "Do not edit manually." Consider adding app/lingo-dynamic-source.tsx to .gitignore to avoid merge conflicts and noisy diffs. Regeneration can happen on demand via the API route.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/lingo-dynamic-source.tsx` around lines 1 - 53, The
auto-generated component LingoDynamicSource is being committed; stop committing
it by adding "app/lingo-dynamic-source.tsx" to .gitignore and remove the tracked
file from git (git rm --cached) so future regenerations from the /api/lingo-sync
route won't cause noisy diffs or merge conflicts; ensure your CI/build still
runs the generator or documents how to regenerate the file on demand.
community/lingo-launch/theme/ThemeProvider.tsx (2)

16-16: Unsafe cast of localStorage value.

localStorage.getItem('theme') as Theme blindly trusts the stored value. If localStorage is corrupted or tampered with, this could set an invalid theme string.

Proposed fix
-        const savedTheme = localStorage.getItem('theme') as Theme || 'light'
+        const stored = localStorage.getItem('theme');
+        const savedTheme: Theme = stored === 'dark' ? 'dark' : 'light';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/theme/ThemeProvider.tsx` at line 16, The code unsafely
casts localStorage.getItem('theme') to Theme; update ThemeProvider to validate
the stored value before using it by adding a type guard for the Theme type
(e.g., isTheme(value): value is Theme) and replace the direct cast for
savedTheme with a safe lookup that returns the stored value only if
isTheme(localValue) is true, otherwise fall back to a default like 'light' so
corrupted/tampered values cannot propagate.

12-27: Theme flash (FOUC) on page load for dark-mode users.

The initial state is "light", and localStorage is read only after mount in an effect. This means dark-mode users will see a flash of the light theme on every navigation. Additionally, Effect 2 (lines 20-27) runs before the state update from Effect 1 propagates, briefly writing "light" back to localStorage.

A common fix is to initialize state from localStorage synchronously (guarded for SSR), or inject a blocking <script> in the <head> that sets the dark class before paint:

Proposed fix — lazy initializer
-    const [theme, setTheme] = useState<Theme>("light")
-
-    useEffect(() => {
-        const savedTheme = localStorage.getItem('theme') as Theme || 'light'
-        setTheme(savedTheme)
-    }, [])
+    const [theme, setTheme] = useState<Theme>(() => {
+        if (typeof window !== 'undefined') {
+            return (localStorage.getItem('theme') as Theme) || 'light';
+        }
+        return 'light';
+    })

Note: With React SSR, useState initializers still run on the server, so the typeof window guard is needed. For a fully flicker-free experience, a blocking script in <head> is the gold standard.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/theme/ThemeProvider.tsx` around lines 12 - 27,
ThemeProvider currently seeds theme state as "light" and reads localStorage in a
mount effect, causing a flash and an immediate overwrite; change the useState
initializer to a lazy initializer that synchronously reads localStorage when
available (guarded with typeof window !== 'undefined') so theme is correct on
first render, remove the first useEffect that reads savedTheme, and keep the
existing effect that applies the document.documentElement.classList change and
writes localStorage (the effect that depends on theme) so subsequent updates
still sync; refer to ThemeProvider, the theme/setTheme state, and the useEffect
that adds/removes 'dark' for locating the code to modify.
community/lingo-launch/app/components/SectionRenderer.tsx (1)

3-4: Unused imports: useLingo and useLingoLocale are imported but never used.

Line 4 imports useLingo and useLingoLocale from lingo.dev/react-client, but neither is referenced anywhere in this file.

🧹 Remove unused imports
 import { useLingoContext } from '@lingo.dev/compiler/react';
-import { useLingo, useLingoLocale } from 'lingo.dev/react-client';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/components/SectionRenderer.tsx` around lines 3 -
4, The file imports unused hooks useLingo and useLingoLocale from
'lingo.dev/react-client'; remove those imports to avoid dead code and lint
failures. Open SectionRenderer.tsx, locate the import line that contains
useLingo and useLingoLocale and delete those two symbols so only useLingoContext
(or any remaining used imports) are imported.
community/lingo-launch/app/components/LanguageSwitcher.tsx (1)

71-71: as any cast on setLocale suppresses type checking.

If setLocale expects a specific union type (e.g., 'en' | 'es' | 'de' | 'fr' | 'hi'), the language.code strings in the languages array should be typed accordingly instead of casting to any.

🔧 Type-safe fix
-const languages = [
+const languages: { code: 'en' | 'es' | 'de' | 'fr' | 'hi'; label: string }[] = [
     { code: 'en', label: 'English' },
     { code: 'es', label: 'Español' },
     { code: 'de', label: 'Deutsch' },
     { code: 'fr', label: 'Français' },
     { code: 'hi', label: 'हिंदी' },
 ];

Then:

-                                    setLocale(language.code as any);
+                                    setLocale(language.code);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/components/LanguageSwitcher.tsx` at line 71, The
code uses an unsafe "as any" cast when calling setLocale(setLocale) with
language.code; instead make this type-safe by giving the languages array (or the
Language type) a precise union type for code (e.g., type Locale = 'en' | 'es' |
'de' | 'fr' | 'hi') and update LanguageSwitcher to use that type so
language.code already matches the expected parameter type of setLocale (or, if
setLocale's type is declared elsewhere, narrow language.code with a proper cast
to that Locale type rather than any). Update the Language type or languages
constant and/or the setLocale prop type so setLocale(language.code) needs no "as
any".
community/lingo-launch/app/lib/storage.ts (1)

138-145: updatePage spreads Partial<SitePage> without filtering out immutable fields.

{ ...pages[index], ...updates, updatedAt: ... } allows callers to accidentally overwrite id, userId, and createdAt. Consider destructuring out the fields that should never change:

🛡️ Proposed fix
 export function updatePage(pageId: string, updates: Partial<SitePage>): SitePage | null {
   const pages = getAllPages();
   const index = pages.findIndex((p) => p.id === pageId);
   if (index === -1) return null;
-  pages[index] = { ...pages[index], ...updates, updatedAt: new Date().toISOString() };
+  const { id: _id, userId: _uid, createdAt: _ca, ...safeUpdates } = updates;
+  pages[index] = { ...pages[index], ...safeUpdates, updatedAt: new Date().toISOString() };
   savePages(pages);
   return pages[index];
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/lib/storage.ts` around lines 138 - 145, The
updatePage function currently spreads the incoming Partial<SitePage> directly
into pages[index], allowing immutable fields to be overwritten; update the
function (updatePage and its use of pages[index]) to explicitly exclude id,
userId, and createdAt from incoming updates by building a sanitized update
object containing only allowed mutable fields (or by destructuring updates to
remove those keys) and then merge that sanitized object with the existing page
and set updatedAt before calling savePages.
community/lingo-launch/app/editor/[pageId]/page.tsx (1)

216-222: setTimeout in handleSavePageInfo is not cleaned up on unmount.

If the user navigates away within 2 seconds of saving, setSaved(false) fires on an unmounted component. While React 19 suppresses the warning, it's cleaner to store the timer ID and clear it.

🧹 Suggested improvement
+  const savedTimerRef = useRef<ReturnType<typeof setTimeout>>();
+
   const handleSavePageInfo = () => {
     if (!page) return;
     updatePage(page.id, { title: title.trim(), description: description.trim() });
     setSaved(true);
-    setTimeout(() => setSaved(false), 2000);
+    clearTimeout(savedTimerRef.current);
+    savedTimerRef.current = setTimeout(() => setSaved(false), 2000);
     loadPage();
   };
+
+  useEffect(() => {
+    return () => clearTimeout(savedTimerRef.current);
+  }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/editor/`[pageId]/page.tsx around lines 216 - 222,
The setTimeout created in handleSavePageInfo can call setSaved(false) after the
component unmounts; change it to store the timer ID (e.g., in a useRef like
saveTimerRef) and clear any existing timer before creating a new one, then clear
the timer in a useEffect cleanup on unmount; update handleSavePageInfo (which
calls updatePage, setSaved, loadPage) to use the ref-based timer management so
setSaved(false) is never invoked after unmount.
community/lingo-launch/app/components/Providers.tsx (1)

15-17: Hardcoded validLocales list and as any cast.

validLocales duplicates the targets array from i18n.json — these will silently diverge if a new target locale is added to the config but not here. Consider importing or deriving the list from a single source of truth. The as any cast on setLocale can be replaced with as string once useLingoContext typings are confirmed:

-setLocale(savedLocale as any);
+setLocale(savedLocale as string);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/components/Providers.tsx` around lines 15 - 17,
Replace the hardcoded validLocales array and the unsafe "as any" cast by
deriving the locale list from the single source of truth (i18n.json targets) and
tightening typings: import or require the targets array from i18n.json (or
export it from your i18n helper) and use that instead of validLocales in the
conditional, and change setLocale(savedLocale as any) to setLocale(savedLocale
as string) after verifying useLingoContext's setLocale accepts string; update
imports and types where needed so valid locale checking and setLocale use the
same typed source.
community/lingo-launch/app/components/Navbar.tsx (1)

33-96: Consider adding aria-label to the <nav> element for accessibility.

Screen readers benefit from a label to distinguish this navigation landmark from others on the page.

♻️ Suggested fix
-    <nav className="sticky top-0 z-50 bg-card/80 backdrop-blur-md border-b border-border">
+    <nav aria-label="Main navigation" className="sticky top-0 z-50 bg-card/80 backdrop-blur-md border-b border-border">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/components/Navbar.tsx` around lines 33 - 96, The
nav landmark in the Navbar component (Navbar.tsx) is missing an aria-label for
screen readers; update the <nav> element in the Navbar component to include a
clear, descriptive ARIA label (e.g., aria-label="Main navigation" or "Primary
navigation") so assistive tech can distinguish this navigation region.
community/lingo-launch/app/page.tsx (1)

17-57: Hardcoded hex colors bypass the design token system.

Lines 30-32 use hardcoded hex values (#0ea5e9, #8b5cf6) for the gradient, while the rest of the page consistently uses design tokens like chart-2, chart-5, primary, etc. Consider using theme-aware tokens here for consistency and to ensure the gradient adapts to theme changes (light/dark).

♻️ Suggested fix
-            <span className="bg-gradient-to-r from-[`#0ea5e9`] to-[`#8b5cf6`] bg-clip-text text-transparent">
+            <span className="bg-gradient-to-r from-chart-2 to-chart-5 bg-clip-text text-transparent">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/page.tsx` around lines 17 - 57, The gradient in
the hero H1 is using hardcoded hex colors in the span (the bg-gradient-to-r
from-[`#0ea5e9`] to-[`#8b5cf6`] class) which bypasses theme tokens; replace those
hex values with theme-aware design tokens or CSS vars (e.g., use your existing
token classes like from-chart-2 to-chart-5 or from-[var(--token-chart-2)]
to-[var(--token-chart-5)] / utility classes that map to tokens) so the gradient
uses the same token names as the rest of the page and will adapt to light/dark
themes; update the span that holds "Launch Everywhere" accordingly.
community/lingo-launch/app/layout.tsx (1)

27-39: Set <html lang> dynamically based on selected locale.

The <html lang="en"> is hardcoded, but LingoLaunch supports multiple locales (en, es, de, fr, hi) via client-side context. The lang attribute impacts accessibility, search engines, and browser spell-check. Currently, locale state lives in localStorage (see LocaleSync in Providers.tsx), making server-side lang attribution difficult in Next.js App Router without middleware or a [locale] routing segment. Track as a follow-up improvement for the community project.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/layout.tsx` around lines 27 - 39, The root layout
currently hardcodes <html lang="en">; update it so the lang attribute is set
dynamically from the app's locale instead of "en". Locate the RootLayout in
layout.tsx and use a server-determined locale prop (or a routing
segment/middleware) to pass the current locale into the RootLayout and set html
lang accordingly; if you opt to keep client-side locale via Providers.LocaleSync
(in Providers.tsx) then add a short-term approach to mirror that locale to the
server (e.g., add a [locale] route segment or middleware to read and inject
locale) and ensure the <html> element reads that locale prop rather than the
hardcoded "en". Reference symbols: layout.tsx RootLayout/return block, Providers
component, and LocaleSync in Providers.tsx.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@community/lingo-launch/app/api/lingo-sync/route.ts`:
- Around line 9-55: The POST handler (exported POST) currently writes files
(filePath / lingo-dynamic-source.tsx) and executes a shell command via
execAsync('npx lingo run') with no auth; add an authentication/authorization
gate at the top of POST that validates a secret/API key from headers or session
against a server-side env var (e.g., process.env.LINGO_SYNC_KEY) and immediately
return a 401/403 NextResponse when missing/invalid; additionally validate and
sanitize the incoming texts array (ensure strings, length limits, escape
content) before building fileContent, restrict filePath to a known safe location
and prevent path traversal, and only run execAsync when auth succeeds—log and
return errors safely using NextResponse with appropriate status codes.
- Line 46: The execAsync call that runs 'npx lingo run' can hang indefinitely;
update the call site in the route handler where execAsync is used to pass a
timeout (or an AbortSignal) to the spawn/exec options so the child is killed
after a reasonable period (e.g., 30s), and add explicit error handling for the
timeout case to log/return a clear error response; locate the execAsync
invocation (the const { stdout, stderr } = await execAsync('npx lingo run', {
cwd: process.cwd() }); line) and modify its options to include timeout/abort and
wrap the await in a try/catch that handles and logs timeout errors and ensures
the child process is cleaned up.
- Line 40: The sync call fs.writeFileSync(filePath, fileContent, 'utf-8') blocks
the event loop; replace it with the async promise-based API
(fs.promises.writeFile or imported fs.promises) and await it inside the request
handler (make the handler async if not already), and wrap the await in try/catch
to surface and log errors before returning the response. Target the
fs.writeFileSync(...) invocation in route.ts (inside the API request handler
function) and change to an awaited fs.promises.writeFile(...) call with proper
error handling.
- Line 31: The generated TypeScript interpolation in texts.map that emits
{t("${text.replace(/"/g, '\\"')}")} is vulnerable to code injection because only
double quotes are escaped; update the sanitizer used before interpolation (the
texts.map arrow callback) to first escape backslashes, then escape double
quotes, backticks, sequence `${` (or replace/escape both '$' and '{'), and
convert newlines to safe escape sequences so user input cannot break out of the
t() call or inject template expressions; ensure this robust escaping is applied
wherever the same pattern is used and keep the sanitized value passed into t()
(reference the texts.map(...) expression and the t(...) call).

In `@community/lingo-launch/app/components/Providers.tsx`:
- Around line 27-31: LocaleSync currently returns null while mounted is false,
blocking ThemeProvider and all children and causing a blank-page flash; change
LocaleSync so it always returns the children (do not gate render on the mounted
flag) and instead perform locale restoration asynchronously (e.g., in useEffect)
to call your locale-setting logic after mount; specifically, remove the early
"if (!mounted) return null" behavior in the LocaleSync component and move any
localStorage read / setLocale calls into an effect that runs on mount so
ThemeProvider and children render immediately while the saved locale is applied
once available.

In `@community/lingo-launch/app/components/SectionRenderer.tsx`:
- Around line 9-10: The code currently destructures `{ t: translate }` from
useLingoContext (in SectionRenderer) which is undefined at runtime; replace that
with the proper hook `useTranslation()` to obtain the `t` function (e.g., const
{ t: translate } = useTranslation()), remove the `// `@ts-ignore`` line, and
update all section render branches so translation is applied consistently —
change hero, features, and text sections to call `translate(...)` for any
user-facing strings the same way cta and testimonial sections do, and ensure the
existing translate calls (used in SectionRenderer render functions around the
cta/testimonial branches) keep the correct `translate` symbol.

In `@community/lingo-launch/app/dashboard/page.tsx`:
- Around line 53-56: handleDelete currently performs a destructive delete
immediately; update it to prompt the user for confirmation before proceeding
(e.g., use window.confirm or open a confirmation modal) and only call
deletePage(pageId) and setPages(...) when the user confirms; keep the function
signature handleDelete(pageId: string) and ensure any modal result or
window.confirm boolean gates both deletePage and the state update to prevent
accidental permanent deletions.
- Around line 86-146: The modal opened when showCreate is true lacks Escape-key
and backdrop-click dismissal; add an onKeyDown on the overlay element (the fixed
inset-0 div) that listens for e.key === 'Escape' and calls setShowCreate(false),
and add an onClick on the same overlay that closes the modal unless the click is
inside the inner modal—achieve this by adding onClick={() =>
setShowCreate(false)} to the overlay and stopping propagation on the inner modal
div (the element with classes "bg-card rounded-2xl ...") via onClick={(e) =>
e.stopPropagation()}; ensure focus is managed so Escape works (keep autoFocus on
the Page Title input) and existing handlers like handleCreate remain unchanged.

In `@community/lingo-launch/app/lib/storage.ts`:
- Around line 1-6: The User interface and the login/register flows in storage.ts
currently accept and persist plaintext passwords; update the implementation to
avoid normalizing insecure patterns by either hashing passwords client-side
using crypto.subtle.digest (e.g., hash the password in register and compare
hashed values in login) before saving to localStorage, or if you intentionally
keep plaintext for demo simplicity, add a prominent top-of-file comment above
the User interface and the login/register functions stating this is for
demonstration only and not secure for production. Ensure you reference and
update the User interface, and the register and login functions to consistently
use the chosen approach.

In `@community/lingo-launch/app/login/page.tsx`:
- Around line 21-27: The current storage flow saves plaintext passwords and
compares them directly; update app/lib/storage.ts so register and login
asynchronously hash passwords (e.g., SHA-256 via the Web Crypto API) and
store/compare the hex/base64 hash instead of the raw password, making both
register and login return Promises; then update the login flow in page.tsx to
await the async login call (await login(email, password)), handle the resolved
user or error, and keep using router.push("/dashboard") on success and
setError/setLoading on failure.

In `@community/lingo-launch/eslint.config.mjs`:
- Around line 1-3: Update the devDependencies entry for "eslint" in package.json
from the broad "^9" range to "^9.22.0" so that imports used in the flat config
(defineConfig and globalIgnores from "eslint/config") are available; modify the
"eslint" version string only (leave other deps like eslint-config-next as-is)
and run npm/yarn install to lock the new minimum version.

---

Nitpick comments:
In `@community/lingo-launch/app/components/LanguageSwitcher.tsx`:
- Line 71: The code uses an unsafe "as any" cast when calling
setLocale(setLocale) with language.code; instead make this type-safe by giving
the languages array (or the Language type) a precise union type for code (e.g.,
type Locale = 'en' | 'es' | 'de' | 'fr' | 'hi') and update LanguageSwitcher to
use that type so language.code already matches the expected parameter type of
setLocale (or, if setLocale's type is declared elsewhere, narrow language.code
with a proper cast to that Locale type rather than any). Update the Language
type or languages constant and/or the setLocale prop type so
setLocale(language.code) needs no "as any".

In `@community/lingo-launch/app/components/Navbar.tsx`:
- Around line 33-96: The nav landmark in the Navbar component (Navbar.tsx) is
missing an aria-label for screen readers; update the <nav> element in the Navbar
component to include a clear, descriptive ARIA label (e.g., aria-label="Main
navigation" or "Primary navigation") so assistive tech can distinguish this
navigation region.

In `@community/lingo-launch/app/components/Providers.tsx`:
- Around line 15-17: Replace the hardcoded validLocales array and the unsafe "as
any" cast by deriving the locale list from the single source of truth (i18n.json
targets) and tightening typings: import or require the targets array from
i18n.json (or export it from your i18n helper) and use that instead of
validLocales in the conditional, and change setLocale(savedLocale as any) to
setLocale(savedLocale as string) after verifying useLingoContext's setLocale
accepts string; update imports and types where needed so valid locale checking
and setLocale use the same typed source.

In `@community/lingo-launch/app/components/SectionRenderer.tsx`:
- Around line 3-4: The file imports unused hooks useLingo and useLingoLocale
from 'lingo.dev/react-client'; remove those imports to avoid dead code and lint
failures. Open SectionRenderer.tsx, locate the import line that contains
useLingo and useLingoLocale and delete those two symbols so only useLingoContext
(or any remaining used imports) are imported.

In `@community/lingo-launch/app/editor/`[pageId]/page.tsx:
- Around line 216-222: The setTimeout created in handleSavePageInfo can call
setSaved(false) after the component unmounts; change it to store the timer ID
(e.g., in a useRef like saveTimerRef) and clear any existing timer before
creating a new one, then clear the timer in a useEffect cleanup on unmount;
update handleSavePageInfo (which calls updatePage, setSaved, loadPage) to use
the ref-based timer management so setSaved(false) is never invoked after
unmount.

In `@community/lingo-launch/app/layout.tsx`:
- Around line 27-39: The root layout currently hardcodes <html lang="en">;
update it so the lang attribute is set dynamically from the app's locale instead
of "en". Locate the RootLayout in layout.tsx and use a server-determined locale
prop (or a routing segment/middleware) to pass the current locale into the
RootLayout and set html lang accordingly; if you opt to keep client-side locale
via Providers.LocaleSync (in Providers.tsx) then add a short-term approach to
mirror that locale to the server (e.g., add a [locale] route segment or
middleware to read and inject locale) and ensure the <html> element reads that
locale prop rather than the hardcoded "en". Reference symbols: layout.tsx
RootLayout/return block, Providers component, and LocaleSync in Providers.tsx.

In `@community/lingo-launch/app/lib/storage.ts`:
- Around line 138-145: The updatePage function currently spreads the incoming
Partial<SitePage> directly into pages[index], allowing immutable fields to be
overwritten; update the function (updatePage and its use of pages[index]) to
explicitly exclude id, userId, and createdAt from incoming updates by building a
sanitized update object containing only allowed mutable fields (or by
destructuring updates to remove those keys) and then merge that sanitized object
with the existing page and set updatedAt before calling savePages.

In `@community/lingo-launch/app/lingo-dynamic-source.tsx`:
- Around line 1-53: The auto-generated component LingoDynamicSource is being
committed; stop committing it by adding "app/lingo-dynamic-source.tsx" to
.gitignore and remove the tracked file from git (git rm --cached) so future
regenerations from the /api/lingo-sync route won't cause noisy diffs or merge
conflicts; ensure your CI/build still runs the generator or documents how to
regenerate the file on demand.

In `@community/lingo-launch/app/page.tsx`:
- Around line 17-57: The gradient in the hero H1 is using hardcoded hex colors
in the span (the bg-gradient-to-r from-[`#0ea5e9`] to-[`#8b5cf6`] class) which
bypasses theme tokens; replace those hex values with theme-aware design tokens
or CSS vars (e.g., use your existing token classes like from-chart-2 to-chart-5
or from-[var(--token-chart-2)] to-[var(--token-chart-5)] / utility classes that
map to tokens) so the gradient uses the same token names as the rest of the page
and will adapt to light/dark themes; update the span that holds "Launch
Everywhere" accordingly.

In `@community/lingo-launch/next.config.ts`:
- Around line 5-14: The exported async function is anonymous which hurts stack
traces; give it a descriptive name (for example getNextConfig or
createNextConfig) while keeping it as the default export so stack traces show
that function name; locate the anonymous default export function in
next.config.ts (the exported async function returning withLingo(nextConfig,
{...})) and change its declaration to a named function (e.g., export default
async function getNextConfig(): Promise<NextConfig>) so the symbol appears in
error traces and logs.

In `@community/lingo-launch/theme/ThemeProvider.tsx`:
- Line 16: The code unsafely casts localStorage.getItem('theme') to Theme;
update ThemeProvider to validate the stored value before using it by adding a
type guard for the Theme type (e.g., isTheme(value): value is Theme) and replace
the direct cast for savedTheme with a safe lookup that returns the stored value
only if isTheme(localValue) is true, otherwise fall back to a default like
'light' so corrupted/tampered values cannot propagate.
- Around line 12-27: ThemeProvider currently seeds theme state as "light" and
reads localStorage in a mount effect, causing a flash and an immediate
overwrite; change the useState initializer to a lazy initializer that
synchronously reads localStorage when available (guarded with typeof window !==
'undefined') so theme is correct on first render, remove the first useEffect
that reads savedTheme, and keep the existing effect that applies the
document.documentElement.classList change and writes localStorage (the effect
that depends on theme) so subsequent updates still sync; refer to ThemeProvider,
the theme/setTheme state, and the useEffect that adds/removes 'dark' for
locating the code to modify.

Comment on lines +9 to +55
export async function POST(req: Request) {
try {
const { texts } = await req.json();

if (!Array.isArray(texts) || texts.length === 0) {
return NextResponse.json({ message: 'No texts provided' }, { status: 400 });
}

// Generate the dummy source file content
// We import useLingo but don't use the hook or component logic, just static t() calls
// so the compiler picks them up.
// Actually, lingo might need a valid react component structure.
const fileContent = `
// This file is auto-generated by LingoLaunch to enable dynamic content translation.
// Do not edit manually.
import { useLingo } from '@lingo.dev/compiler/react';

export default function LingoDynamicSource() {
const { t } = useLingo();

return (
<>
${texts.map(text => `{/* @ts-ignore */}\n {t("${text.replace(/"/g, '\\"')}")}`).join('\n ')}
</>
);
}
`;

const filePath = path.join(process.cwd(), 'app', 'lingo-dynamic-source.tsx');

// Write the file
fs.writeFileSync(filePath, fileContent, 'utf-8');

// Run lingo run
// Using npx lingo run. Assuming it's available in the environment.
// We might need to handle the output/error.
// CWD should be project root.
const { stdout, stderr } = await execAsync('npx lingo run', { cwd: process.cwd() });

console.log('Lingo Run Output:', stdout);
if (stderr) console.error('Lingo Run Error:', stderr);

return NextResponse.json({ success: true, message: 'Translations updated' });
} catch (error) {
console.error('Error in lingo-sync:', error);
return NextResponse.json({ error: 'Failed to sync translations' }, { status: 500 });
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Unauthenticated endpoint writes arbitrary files and executes shell commands.

This POST handler has no authentication or authorization. Any client can trigger file writes to disk and shell command execution (npx lingo run). In a deployed environment, this is a remote code execution vector. At minimum, gate this behind an auth check or an API key.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/api/lingo-sync/route.ts` around lines 9 - 55, The
POST handler (exported POST) currently writes files (filePath /
lingo-dynamic-source.tsx) and executes a shell command via execAsync('npx lingo
run') with no auth; add an authentication/authorization gate at the top of POST
that validates a secret/API key from headers or session against a server-side
env var (e.g., process.env.LINGO_SYNC_KEY) and immediately return a 401/403
NextResponse when missing/invalid; additionally validate and sanitize the
incoming texts array (ensure strings, length limits, escape content) before
building fileContent, restrict filePath to a known safe location and prevent
path traversal, and only run execAsync when auth succeeds—log and return errors
safely using NextResponse with appropriate status codes.


return (
<>
${texts.map(text => `{/* @ts-ignore */}\n {t("${text.replace(/"/g, '\\"')}")}`).join('\n ')}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Code injection via insufficient input sanitization.

Only double quotes are escaped (text.replace(/"/g, '\\"')), but the user-supplied text is interpolated into a generated TypeScript source file. An attacker can inject arbitrary code through:

  • Backslash sequences (e.g., \" to un-escape the quote)
  • Template literal expressions if the outer template uses backticks
  • Newlines to break out of the t() call

At a minimum, sanitize backslashes before quotes, and also handle ${}, backticks, and newlines:

Proposed safer escaping
-      ${texts.map(text => `{/* `@ts-ignore` */}\n      {t("${text.replace(/"/g, '\\"')}")}`).join('\n      ')}
+      ${texts.map(text => {
+        const safe = text
+          .replace(/\\/g, '\\\\')
+          .replace(/"/g, '\\"')
+          .replace(/\n/g, '\\n')
+          .replace(/\r/g, '\\r')
+          .replace(/`/g, '\\`')
+          .replace(/\$\{/g, '\\${');
+        return `{/* `@ts-ignore` */}\n      {t("${safe}")}`;
+      }).join('\n      ')}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
${texts.map(text => `{/* @ts-ignore */}\n {t("${text.replace(/"/g, '\\"')}")}`).join('\n ')}
${texts.map(text => {
const safe = text
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/`/g, '\\`')
.replace(/\$\{/g, '\\${');
return `{/* `@ts-ignore` */}\n {t("${safe}")}`;
}).join('\n ')}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/api/lingo-sync/route.ts` at line 31, The generated
TypeScript interpolation in texts.map that emits {t("${text.replace(/"/g,
'\\"')}")} is vulnerable to code injection because only double quotes are
escaped; update the sanitizer used before interpolation (the texts.map arrow
callback) to first escape backslashes, then escape double quotes, backticks,
sequence `${` (or replace/escape both '$' and '{'), and convert newlines to safe
escape sequences so user input cannot break out of the t() call or inject
template expressions; ensure this robust escaping is applied wherever the same
pattern is used and keep the sanitized value passed into t() (reference the
texts.map(...) expression and the t(...) call).

const filePath = path.join(process.cwd(), 'app', 'lingo-dynamic-source.tsx');

// Write the file
fs.writeFileSync(filePath, fileContent, 'utf-8');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Synchronous file write blocks the event loop.

fs.writeFileSync on an API request path blocks all other requests. Use fs.promises.writeFile instead.

Proposed fix
-        fs.writeFileSync(filePath, fileContent, 'utf-8');
+        await fs.promises.writeFile(filePath, fileContent, 'utf-8');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fs.writeFileSync(filePath, fileContent, 'utf-8');
await fs.promises.writeFile(filePath, fileContent, 'utf-8');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/api/lingo-sync/route.ts` at line 40, The sync call
fs.writeFileSync(filePath, fileContent, 'utf-8') blocks the event loop; replace
it with the async promise-based API (fs.promises.writeFile or imported
fs.promises) and await it inside the request handler (make the handler async if
not already), and wrap the await in try/catch to surface and log errors before
returning the response. Target the fs.writeFileSync(...) invocation in route.ts
(inside the API request handler function) and change to an awaited
fs.promises.writeFile(...) call with proper error handling.

// Using npx lingo run. Assuming it's available in the environment.
// We might need to handle the output/error.
// CWD should be project root.
const { stdout, stderr } = await execAsync('npx lingo run', { cwd: process.cwd() });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

exec has no timeout — can hang indefinitely.

If npx lingo run stalls, this request will never resolve and the connection stays open. Add a timeout.

Proposed fix
-        const { stdout, stderr } = await execAsync('npx lingo run', { cwd: process.cwd() });
+        const { stdout, stderr } = await execAsync('npx lingo run', {
+          cwd: process.cwd(),
+          timeout: 60_000, // 60 seconds
+        });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { stdout, stderr } = await execAsync('npx lingo run', { cwd: process.cwd() });
const { stdout, stderr } = await execAsync('npx lingo run', {
cwd: process.cwd(),
timeout: 60_000, // 60 seconds
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/api/lingo-sync/route.ts` at line 46, The execAsync
call that runs 'npx lingo run' can hang indefinitely; update the call site in
the route handler where execAsync is used to pass a timeout (or an AbortSignal)
to the spawn/exec options so the child is killed after a reasonable period
(e.g., 30s), and add explicit error handling for the timeout case to log/return
a clear error response; locate the execAsync invocation (the const { stdout,
stderr } = await execAsync('npx lingo run', { cwd: process.cwd() }); line) and
modify its options to include timeout/abort and wrap the await in a try/catch
that handles and logs timeout errors and ensures the child process is cleaned
up.

Comment on lines +27 to +31
if (!mounted) {
return null; // or a loading spinner to prevent hydration mismatch/flash
}

return <>{children}</>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

LocaleSync blocks all page rendering until after hydration — causes blank-page flash.

When !mounted, LocaleSync returns null, which suppresses ThemeProvider and all page children on every initial render. Because restoring the locale from localStorage is a cosmetic side-effect (the page is usable in the default locale immediately), it should not gate the render. Render children unconditionally and apply the saved locale asynchronously:

🔧 Proposed fix
 function LocaleSync({ children }: { children: React.ReactNode }) {
   const { locale, setLocale } = useLingoContext();
-  const [mounted, setMounted] = useState(false);

   useEffect(() => {
-    setMounted(true);
     const savedLocale = localStorage.getItem('lingolaunch_locale');
     const validLocales = ['en', 'es', 'de', 'fr', 'hi'];
     if (savedLocale && validLocales.includes(savedLocale) && savedLocale !== locale) {
       setLocale(savedLocale as any);
     }
   }, []);

   useEffect(() => {
-    if (mounted && locale) {
+    if (locale) {
       localStorage.setItem('lingolaunch_locale', locale);
     }
-  }, [locale, mounted]);
+  }, [locale]);

-  if (!mounted) {
-    return null;
-  }
-
   return <>{children}</>;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!mounted) {
return null; // or a loading spinner to prevent hydration mismatch/flash
}
return <>{children}</>;
function LocaleSync({ children }: { children: React.ReactNode }) {
const { locale, setLocale } = useLingoContext();
useEffect(() => {
const savedLocale = localStorage.getItem('lingolaunch_locale');
const validLocales = ['en', 'es', 'de', 'fr', 'hi'];
if (savedLocale && validLocales.includes(savedLocale) && savedLocale !== locale) {
setLocale(savedLocale as any);
}
}, []);
useEffect(() => {
if (locale) {
localStorage.setItem('lingolaunch_locale', locale);
}
}, [locale]);
return <>{children}</>;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/components/Providers.tsx` around lines 27 - 31,
LocaleSync currently returns null while mounted is false, blocking ThemeProvider
and all children and causing a blank-page flash; change LocaleSync so it always
returns the children (do not gate render on the mounted flag) and instead
perform locale restoration asynchronously (e.g., in useEffect) to call your
locale-setting logic after mount; specifically, remove the early "if (!mounted)
return null" behavior in the LocaleSync component and move any localStorage read
/ setLocale calls into an effect that runs on mount so ThemeProvider and
children render immediately while the saved locale is applied once available.

Comment on lines +53 to +56
const handleDelete = (pageId: string) => {
deletePage(pageId);
setPages((prev) => prev.filter((p) => p.id !== pageId));
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing confirmation before destructive delete action.

handleDelete immediately removes the page with no confirmation dialog or undo mechanism. A single misclick on the trash icon permanently deletes user content. Add a window.confirm() or a confirmation modal before proceeding.

🛡️ Minimal fix using window.confirm
  const handleDelete = (pageId: string) => {
+   if (!confirm("Are you sure you want to delete this page? This cannot be undone.")) return;
    deletePage(pageId);
    setPages((prev) => prev.filter((p) => p.id !== pageId));
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleDelete = (pageId: string) => {
deletePage(pageId);
setPages((prev) => prev.filter((p) => p.id !== pageId));
};
const handleDelete = (pageId: string) => {
if (!confirm("Are you sure you want to delete this page? This cannot be undone.")) return;
deletePage(pageId);
setPages((prev) => prev.filter((p) => p.id !== pageId));
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/dashboard/page.tsx` around lines 53 - 56,
handleDelete currently performs a destructive delete immediately; update it to
prompt the user for confirmation before proceeding (e.g., use window.confirm or
open a confirmation modal) and only call deletePage(pageId) and setPages(...)
when the user confirms; keep the function signature handleDelete(pageId: string)
and ensure any modal result or window.confirm boolean gates both deletePage and
the state update to prevent accidental permanent deletions.

Comment on lines +86 to +146
{showCreate && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm px-4">
<div className="bg-card rounded-2xl border border-border shadow-xl w-full max-w-md p-6">
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-bold text-card-foreground">
Create New Page
</h2>
<button
onClick={() => setShowCreate(false)}
className="p-1 rounded-md hover:bg-secondary transition-colors"
>
<X className="h-5 w-5 text-muted-foreground" />
</button>
</div>

<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-card-foreground mb-1.5">
Page Title
</label>
<input
type="text"
value={newTitle}
onChange={(e) => setNewTitle(e.target.value)}
placeholder="My Landing Page"
className="w-full px-4 py-2.5 bg-background border border-input rounded-lg text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
autoFocus
onKeyDown={(e) => e.key === "Enter" && handleCreate()}
/>
</div>
<div>
<label className="block text-sm font-medium text-card-foreground mb-1.5">
Description
</label>
<textarea
value={newDesc}
onChange={(e) => setNewDesc(e.target.value)}
placeholder="A brief description of your page"
rows={3}
className="w-full px-4 py-2.5 bg-background border border-input rounded-lg text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring resize-none"
/>
</div>
<div className="flex gap-3 pt-2">
<button
onClick={() => setShowCreate(false)}
className="flex-1 px-4 py-2.5 border border-border rounded-lg text-foreground font-medium hover:bg-secondary transition-colors"
>
Cancel
</button>
<button
onClick={handleCreate}
disabled={!newTitle.trim()}
className="flex-1 bg-primary text-primary-foreground px-4 py-2.5 rounded-lg font-semibold hover:opacity-90 transition-opacity disabled:opacity-50"
>
Create Page
</button>
</div>
</div>
</div>
</div>
)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Modal is missing Escape-key dismissal and backdrop click-to-close.

The create-page modal doesn't close when pressing Escape or clicking the backdrop overlay. Both are standard expectations for modal UX and accessibility (WCAG dialog patterns).

♻️ Suggested improvements

Add an onKeyDown handler on the overlay div and an onClick on the backdrop:

-        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm px-4">
-          <div className="bg-card rounded-2xl border border-border shadow-xl w-full max-w-md p-6">
+        <div
+          className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm px-4"
+          onClick={() => setShowCreate(false)}
+          onKeyDown={(e) => e.key === "Escape" && setShowCreate(false)}
+        >
+          <div
+            className="bg-card rounded-2xl border border-border shadow-xl w-full max-w-md p-6"
+            onClick={(e) => e.stopPropagation()}
+          >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/dashboard/page.tsx` around lines 86 - 146, The
modal opened when showCreate is true lacks Escape-key and backdrop-click
dismissal; add an onKeyDown on the overlay element (the fixed inset-0 div) that
listens for e.key === 'Escape' and calls setShowCreate(false), and add an
onClick on the same overlay that closes the modal unless the click is inside the
inner modal—achieve this by adding onClick={() => setShowCreate(false)} to the
overlay and stopping propagation on the inner modal div (the element with
classes "bg-card rounded-2xl ...") via onClick={(e) => e.stopPropagation()};
ensure focus is managed so Escape works (keep autoFocus on the Page Title input)
and existing handlers like handleCreate remain unchanged.

Comment on lines +1 to +6
export interface User {
id: string;
name: string;
email: string;
password: string;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Plaintext password storage in localStorage.

The User interface stores the password as a plain string, and login/register work with raw plaintext. Even for a demo/community project, this normalizes insecure patterns. Consider at minimum hashing with crypto.subtle.digest before storing, or adding a prominent comment that this is intentional for demo purposes only and not suitable for production.

Also applies to: 53-68

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/lib/storage.ts` around lines 1 - 6, The User
interface and the login/register flows in storage.ts currently accept and
persist plaintext passwords; update the implementation to avoid normalizing
insecure patterns by either hashing passwords client-side using
crypto.subtle.digest (e.g., hash the password in register and compare hashed
values in login) before saving to localStorage, or if you intentionally keep
plaintext for demo simplicity, add a prominent top-of-file comment above the
User interface and the login/register functions stating this is for
demonstration only and not secure for production. Ensure you reference and
update the User interface, and the register and login functions to consistently
use the chosen approach.

Comment on lines +21 to +27
const user = login(email, password);
if (user) {
router.push("/dashboard");
} else {
setError("Invalid email or password. Please try again.");
setLoading(false);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Plaintext passwords stored in and read from localStorage — security risk.

The login function (and register) in app/lib/storage.ts stores the entire User object, including the plain-text password, in localStorage, and authentication is a direct string equality check (u.password === password). Any script executing in the same origin — including via XSS — can enumerate all registered users and their passwords.

At a minimum, derive a hash (e.g. SHA-256 via the Web Crypto API) before persisting passwords and compare hashes on login. Even for a demo, never store or compare raw passwords, as developers routinely reuse credentials:

// In storage.ts register():
-const user: User = { id: generateId(), name, email, password };
+const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(password));
+const passwordHash = Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2,"0")).join("");
+const user: User = { id: generateId(), name, email, password: passwordHash };

// In storage.ts login():
-const user = getUsers().find((u) => u.email === email && u.password === password);
+const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(password));
+const passwordHash = Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2,"0")).join("");
+const user = getUsers().find((u) => u.email === email && u.password === passwordHash);

Note: this also makes login and register async, which needs to be reflected in the page components.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/app/login/page.tsx` around lines 21 - 27, The current
storage flow saves plaintext passwords and compares them directly; update
app/lib/storage.ts so register and login asynchronously hash passwords (e.g.,
SHA-256 via the Web Crypto API) and store/compare the hex/base64 hash instead of
the raw password, making both register and login return Promises; then update
the login flow in page.tsx to await the async login call (await login(email,
password)), handle the resolved user or error, and keep using
router.push("/dashboard") on success and setError/setLoading on failure.

Comment on lines +1 to +3
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat community/lingo-launch/package.json

Repository: lingodotdev/lingo.dev

Length of output: 696


Update eslint version constraint to ^9.22.0 in package.json.

The eslint version is declared as "^9" in devDependencies, which permits any ESLint 9.x version from 9.0.0 onwards. However, defineConfig and globalIgnores from "eslint/config" were first introduced in ESLint v9.22.0. The current constraint risks installing an incompatible version (e.g., 9.0.0–9.21.x) and breaking the configuration at runtime.

Update the constraint to "^9.22.0" to ensure the minimum required version is installed.

The eslint-config-next version (16.1.6) is appropriate and supports the flat-config subpath exports (/core-web-vitals, /typescript) used in lines 2–3.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@community/lingo-launch/eslint.config.mjs` around lines 1 - 3, Update the
devDependencies entry for "eslint" in package.json from the broad "^9" range to
"^9.22.0" so that imports used in the flat config (defineConfig and
globalIgnores from "eslint/config") are available; modify the "eslint" version
string only (leave other deps like eslint-config-next as-is) and run npm/yarn
install to lock the new minimum version.

@mesusheel01 mesusheel01 changed the title Malevolant shrine Lingo-launch@pr1 Feb 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant