Skip to content

Auto commit 20260323 091015#8

Open
WomB0ComB0 wants to merge 9 commits intomainfrom
auto-commit-20260323-091015
Open

Auto commit 20260323 091015#8
WomB0ComB0 wants to merge 9 commits intomainfrom
auto-commit-20260323-091015

Conversation

@WomB0ComB0
Copy link
Copy Markdown
Member

@WomB0ComB0 WomB0ComB0 commented Mar 24, 2026

Summary by CodeRabbit

  • New Features

    • Multi-language support (EN, ES, ZH, HI, AR) with locale switcher and localized content
    • Blog launched with new posts about decentralized mesh networks and disconnected-first design
    • Contact page and interactive contact form; new metrics, trust/security, and loading experiences
  • Documentation

    • Added Platform, Protocol, and comprehensive legal pages (Privacy, Terms, Security, Cookie, DPA, Acceptable Use)
  • Improvements

    • Accessibility enhancements (ARIA/semantic labels) and visual/theme updates; sitemap and routing refinements

@WomB0ComB0 WomB0ComB0 requested a review from Copilot March 24, 2026 17:02
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
landing Error Error Mar 25, 2026 9:24am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

📝 Walkthrough

Walkthrough

Adds full i18n support (en, es, zh, hi, ar), a filesystem-backed MDX blog, multiple marketing/legal pages, locale-aware routing/layouts, translated message catalogs, contact form/server action, various marketing UI components and accessibility updates, sitemap/i18n middleware integration, tests, and multiple config/asset updates.

Changes

Cohort / File(s) Summary
I18n Core & Routing
src/i18n/routing.ts, src/i18n/request.ts, src/i18n/navigation.ts, next.config.ts, src/i18n/request.ts
Added next-intl routing, request-level message loading, navigation helpers, and integrated next-intl plugin into Next.js config; updated redirects and Sentry wrapping.
Locale Message Catalogs
messages/en.json, messages/es.json, messages/zh.json, messages/hi.json, messages/ar.json
Added complete localized UI text for five languages (marketing, nav, footer, forms, legal, etc.).
Blog & Content Layer
src/lib/content.ts, content/posts/*.mdx, src/app/[locale]/(marketing)/blog/page.tsx, src/app/[locale]/(marketing)/blog/[slug]/page.tsx
New MDX post files and content library for discovering/parsing posts; blog index and per-post pages with static params, metadata, and MDX rendering.
Locale Layouts & Pages
src/app/[locale]/layout.tsx, src/app/layout.tsx, src/app/[locale]/loading.tsx, src/app/[locale]/error.tsx, src/app/[locale]/not-found.tsx
Introduced locale-aware root layout, request locale handling, message provider, locale fonts, loading/error UI adjustments, and metadata generation.
Marketing Pages & Layouts
src/app/[locale]/(marketing)/layout.tsx, src/app/(marketing)/layout.tsx, src/app/[locale]/(marketing)/page.tsx, src/app/[locale]/(marketing)/platform/page.tsx, src/app/[locale]/(marketing)/protocol/page.tsx, src/app/[locale]/(marketing)/contact/page.tsx
Added new marketing layout(s) and pages (home composition changes, platform, protocol, contact) with accessible structure and modular sections.
Legal Pages
src/app/[locale]/(marketing)/legal/*
Added many legal pages (privacy, terms, acceptable use, cookie policy, data processing, security) and a legal layout.
Marketing Components & Sections
src/features/marketing/components/*, src/features/marketing/sections/*, src/features/marketing/sections/index.ts
New/updated components: navbar, footer, hero (scenario-driven HUD), hero video slideshow, contact form, locale switcher, hash-scroll center; added MetricsStrip and TrustSecurity sections; multiple accessibility and content updates.
Contact Actions
src/actions/contact/submit.ts, (removed) src/actions/contact/submit-contact.ts
Replaced removed server action with a new Zod-validated submitContact server action.
Proxy & Middleware
src/proxy.ts
Integrated next-intl middleware into proxy flow, moved security headers/nonce handling to response, and widened route matcher.
SEO, Manifest & Sitemap
src/utils/seo/meta.ts, src/app/manifest.ts, src/app/sitemap.ts, public/sitemap.xml
Updated metadata construction, changed PWA shortcuts, replaced static sitemap.xml with dynamic multi-locale sitemap generation.
Styling & Fonts
src/styles/globals.css
Adjusted theme tokens, added popover variables and locale-specific font overrides, set color-scheme.
Env, Types & Config
env.d.ts, src/env.ts, src/config/config.ts, src/constants/app.ts, package.json
Centralized AppProcessEnv, added SENTRY_URL, extended app metadata (links/socials/company), added dependencies (next-intl, mdx, gray-matter, tanstack form), and bumped UI package.
Instrumentation & Logging
instrumentation-client.ts, src/lib/better-stack.ts, src/utils/logger.ts
Removed Sentry tunnel, exported router transition hook, added BetterStack logger factory with stub fallback, normalized env var access.
Utilities & Tests
src/utils/helpers.ts, src/lib/utils.test.ts, src/utils/*.(test).ts, src/mdx-components.tsx
Refactored scroll helpers (centered scrolling and hash parsing), added MDX component helper, and multiple Bun test suites for utilities, logger, and SEO metadata.
Misc / Housekeeping
.gitignore, .github/workflows/ci.yml, many copyright header updates
Small edits: updated .gitignore entries, pinned CI actions, and standardized copyright strings across many files.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant Proxy as Proxy/Middleware
    participant Intl as next-intl Middleware
    participant LocaleLayout as LocaleLayout
    participant Messages as Message Loader
    participant Page as Page Component

    Browser->>Proxy: GET /es/blog
    Proxy->>Intl: intlMiddleware(req)
    Intl->>Proxy: Response (with locale set)
    Proxy->>LocaleLayout: render with locale
    LocaleLayout->>Messages: import messages for 'es'
    Messages->>LocaleLayout: messages JSON
    LocaleLayout->>Page: provide NextIntlClientProvider
    Page->>Browser: Render localized page
Loading
sequenceDiagram
    participant User
    participant BlogPage
    participant ContentLib
    participant FS as Filesystem
    participant MDX as MDXRemote

    User->>BlogPage: Navigate /blog/decentralized-mesh
    BlogPage->>ContentLib: getPostBySlug('decentralized-mesh')
    ContentLib->>FS: read file content
    FS->>ContentLib: file data
    ContentLib->>BlogPage: {meta, content}
    BlogPage->>MDX: render content
    MDX->>User: Rendered HTML
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

area:components

"🐰 Hopping through locales near and far,
Five tongues echo under one bright star.
MDX sprouts posts in morning light,
Routing stitched, the blog takes flight.
A carrot toast to code done right!" 🥕✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch auto-commit-20260323-091015

@socket-security
Copy link
Copy Markdown

socket-security bot commented Mar 24, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​types/​bun@​1.3.111001004993100
Addedcritters@​0.0.259910010050100
Addednext-mdx-remote@​6.0.0991001008870
Added@​tanstack/​react-form@​1.28.51001008399100
Addedgray-matter@​4.0.39910010083100
Addednext-intl@​4.8.31001009392100

View full report

@socket-security
Copy link
Copy Markdown

socket-security bot commented Mar 24, 2026

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm entities is 91.0% likely obfuscated

Confidence: 0.91

Location: Package overview

From: ?npm/critters@0.0.25npm/entities@4.5.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/entities@4.5.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR significantly expands the marketing site by adding locale-aware routing (next-intl), a new blog backed by MDX content, and several new/updated marketing sections, while also tightening SEO metadata and edge security header handling.

Changes:

  • Add next-intl locale routing + message catalogs (en/es/zh/hi/ar) and refactor the app layout structure around [locale].
  • Introduce a new MDX-powered blog (content loader + blog index + blog post pages) and add legal pages (Terms/Privacy).
  • Update marketing UI components/sections (new sections, accessibility improvements) and add/expand unit tests for utilities.

Reviewed changes

Copilot reviewed 73 out of 90 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
test.ts Adds an ad-hoc Sentry test snippet (appears non-production).
src/utils/seo/speculation-schema.test.ts Adds tests for schema graph helpers.
src/utils/seo/meta.ts Updates metadata/title behavior, OG image title, Twitter fields, viewport theme colors.
src/utils/seo/meta.test.ts Adds tests for metadata + viewport helpers.
src/utils/logger.test.ts Adds Logger unit tests (bun:test).
src/utils/helpers.ts Refactors URL helper and adds hash/scroll centering helpers with SSR guards.
src/utils/helpers.test.ts Adds tests for helpers (URL/hash/scroll + stringify).
src/styles/globals.css Updates tokens, adds popover vars and locale font overrides, sets color-scheme.
src/proxy.ts Composes next-intl middleware with security headers/CSP and updates matcher.
src/mdx-components.tsx Adds MDX components passthrough hook.
src/lib/utils.test.ts Adds tests for cn() utility.
src/lib/content.ts Adds MDX post loader (gray-matter + filesystem).
src/i18n/routing.ts Defines locale routing configuration.
src/i18n/request.ts Loads locale messages via next-intl server config.
src/i18n/navigation.ts Adds locale-aware navigation helpers (currently unused).
src/hooks/index.ts Normalizes export quoting/style.
src/features/marketing/sections/why-now.tsx Accessibility tweaks (aria-labelledby, icon aria-hidden) + styling tweaks.
src/features/marketing/sections/use-cases.tsx Adds aria-labelledby and refines Tabs UI + responsive label behavior.
src/features/marketing/sections/trust-security.tsx New marketing section.
src/features/marketing/sections/technology.tsx Adds aria-labelledby + visual emphasis tweak.
src/features/marketing/sections/problem-statement.tsx Adds aria-labelledby, reduced-motion pulse, table labeling and layout tweaks.
src/features/marketing/sections/platform-pillars.tsx Adds aria-labelledby + icon aria-hidden and styling tweaks.
src/features/marketing/sections/metrics-strip.tsx New metrics strip section.
src/features/marketing/sections/index.ts Exports new sections.
src/features/marketing/sections/how-it-works.tsx Adds aria-labelledby.
src/features/marketing/sections/hero.tsx Makes hero slideshow drive HUD scenario; adds aria-labelledby and styling tweaks.
src/features/marketing/sections/footer-cta.tsx Replaces button CTA with inline mailto email form + a11y improvements.
src/features/marketing/sections/competitive-table.tsx Adds aria-labelledby + layout tweaks.
src/features/marketing/sections/architecture-diagram.tsx Adds aria-labelledby + table min-width tweak.
src/features/marketing/components/navbar.tsx Refactors nav items to use translations + improves a11y/mobile behavior.
src/features/marketing/components/hero-video-slideshow.tsx Adds onIndexChange callback and adjusts video rendering.
src/features/marketing/components/hero-hud.tsx Makes HUD scenario-driven and reduced-motion friendly.
src/features/marketing/components/hash-scroll-center.tsx New client helper to center-scroll known hash sections.
src/features/marketing/components/footer.tsx Refactors footer for translations and fetches /api/status for live status.
src/features/marketing/components/contact-form.tsx New TanStack React Form contact form using a safe server action.
src/env.ts Extends env schema (SENTRY_URL), exports inferred ProcessEnv typing.
src/constants/app.ts Expands app metadata (name/desc/email/links/socials/company).
src/config/config.ts Extends runtime config (links/socials/company/betterstack) and adjusts Vercel env source.
src/app/sitemap.ts Replaces static route list with route config (priority/changefreq).
src/app/manifest.ts Updates manifest “blog” entry to new blog naming/URL.
src/app/layout.tsx Refactors root layout to only return children; viewport remains.
src/app/global-error.tsx Adds dark class and improves semantics (main role=alert).
src/app/api/[[...route]]/health/handlers.ts Removes redundant @version tags in docs.
src/app/[locale]/not-found.tsx Adds metadata and minor layout/icon a11y tweaks.
src/app/[locale]/loading.tsx Adds reduced-motion handling + aria-busy/progressbar semantics and styling tweaks.
src/app/[locale]/layout.tsx New locale layout: fonts, direction (rtl for ar), messages provider, static params.
src/app/[locale]/forbidden.tsx Removes per-page metadata construction.
src/app/[locale]/error.tsx Improves typing + semantics (main role=alert) and import ordering.
src/app/[locale]/(marketing)/protocol/page.tsx New Protocol marketing page with metadata.
src/app/[locale]/(marketing)/platform/page.tsx New Platform marketing page with metadata.
src/app/[locale]/(marketing)/page.tsx Updates home composition (adds MetricsStrip, removes some sections).
src/app/[locale]/(marketing)/legal/terms/page.tsx New Terms page with metadata.
src/app/[locale]/(marketing)/legal/privacy/page.tsx New Privacy page with metadata.
src/app/[locale]/(marketing)/legal/layout.tsx New legal layout wrapper.
src/app/[locale]/(marketing)/layout.tsx New marketing layout (navbar/footer/hash scroll + skip link + main wrapper).
src/app/[locale]/(marketing)/contact/page.tsx New Contact page integrating ContactForm + external resource links.
src/app/[locale]/(marketing)/blog/page.tsx New Blog index page listing MDX posts.
src/app/[locale]/(marketing)/blog/[slug]/page.tsx New Blog post page rendering MDX + dynamic metadata.
src/app/(marketing)/layout.tsx Removes old non-locale marketing layout.
src/actions/contact/submit.ts Adds new server action for contact submission (zod validated).
src/actions/contact/submit-contact.ts Removes prior server action implementation.
public/sitemap.xml Removes static sitemap file (replaced by Next sitemap route).
public/assets/videos/drone-landing-video.mp4 Adds marketing video asset.
public/assets/videos/Futuristic_Drone_Lands_On_Logo.mp4 Adds marketing video asset.
public/assets/images/traffic-violation.webp Adds marketing image asset.
public/assets/images/traffic-violation.jpeg Adds marketing image asset.
public/assets/images/swat-and-technical.webp Adds marketing image asset.
public/assets/images/swat-and-technical.jpeg Adds marketing image asset.
public/assets/images/natural-disasters.webp Adds marketing image asset.
public/assets/images/natural-disasters.jpeg Adds marketing image asset.
public/assets/images/mass-casualty-incident.webp Adds marketing image asset.
public/assets/images/mass-casualty-incident.jpeg Adds marketing image asset.
public/assets/images/car-crashes.webp Adds marketing image asset.
public/assets/images/car-crashes.jpeg Adds marketing image asset.
public/assets/images/active-fires.webp Adds marketing image asset.
public/assets/images/active-fires.jpeg Adds marketing image asset.
package.json Adds deps for forms, MDX, next-intl, gray-matter, critters, bun types.
next.config.ts Adds next-intl plugin integration; adjusts redirects and Sentry build config.
messages/zh.json Adds Chinese translations.
messages/hi.json Adds Hindi translations.
messages/es.json Adds Spanish translations.
messages/en.json Adds English translations.
messages/ar.json Adds Arabic translations.
instrumentation-client.ts Adjusts Sentry client setup and exports router transition instrumentation.
env.d.ts Replaces manual NODE_ENV typing with inferred env contract.
content/posts/introducing-resq-tactical-os.mdx Adds MDX blog content.
content/posts/decentralized-mesh-networks-save-lives.mdx Adds MDX blog content.
content/posts/building-for-zero-connectivity.mdx Adds MDX blog content.
.gitignore Adds additional ignored directories/files.
Comments suppressed due to low confidence (1)

src/proxy.ts:100

  • CSP script-src includes a per-request nonce + strict-dynamic, but the nonce is no longer attached to the request (it’s only set as a response header). Without propagating the nonce into the rendering pipeline (e.g., via NextResponse.next({ request: { headers }}) or another supported Next nonce mechanism), Next.js inline scripts won’t receive the nonce and may be blocked in production. Please restore request-header propagation (and ensure it composes correctly with intlMiddleware) or relax the CSP accordingly.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +172 to +174
<NavigationMenuContent className="overflow-visible!">
<ul className="grid w-[min(400px,90vw)] gap-1 p-2 md:w-112.5 md:grid-cols-2">
{platformItems.map((item) => (
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

className="overflow-visible!" looks like an invalid Tailwind “important” modifier (Tailwind uses !overflow-visible, not a trailing !). As written, the class will likely be ignored and the menu content may still be clipped. Please replace with the correct utility (or an explicit style) so the dropdown can overflow as intended.

Copilot uses AI. Check for mistakes.
Comment on lines +185 to +187
<NavigationMenuContent className="overflow-visible!">
<ul className="grid w-[min(400px,90vw)] gap-1 p-2 md:w-125 md:grid-cols-2">
{developerItems.map((item) => (
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

Same issue here: className="overflow-visible!" is likely not a valid Tailwind important modifier and may not apply. Consider switching to !overflow-visible (or another supported approach) to avoid dropdown clipping.

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +8
import { createNavigation } from "next-intl/navigation";
import { routing } from "./routing";

export const { Link, redirect, usePathname, useRouter, getPathname } =
createNavigation(routing);
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

This locale-aware navigation wrapper isn’t referenced anywhere else in the repo (no imports found). If it’s not going to be used, consider removing it to avoid dead code; otherwise, update internal navigation to consume these helpers so locale prefixing is consistent across the app.

Copilot uses AI. Check for mistakes.
test.ts Outdated
Comment on lines +1 to +2
import * as Sentry from "@sentry/browser";
(() => console.log(Sentry.captureMessage("Hello Better Stack, this is a test message from JavaScript!")))(); No newline at end of file
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

This file looks like an ad-hoc Sentry smoke-test (imports @sentry/browser and immediately captures/logs a message). If it’s not part of the shipped app/test suite, it should be removed or moved to a dedicated script/examples area to avoid accidental execution/packaging in CI or production builds.

Copilot uses AI. Check for mistakes.
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly upgrades the application's infrastructure by integrating robust internationalization capabilities and a flexible blog content management system. It also includes crucial updates to environment variable handling, Sentry monitoring, and introduces new legal and contact pages. These changes, coupled with various UI/UX enhancements and dependency updates, aim to improve the application's global reach, content delivery, maintainability, and overall user experience.

Highlights

  • Internationalization (i18n) Integration: The application now supports multiple languages (Arabic, English, Spanish, Hindi, Chinese) through next-intl, with new message files and locale-aware routing and layouts.
  • Blog Content Management System (CMS): A new blog feature has been added, utilizing MDX files for content and next-mdx-remote for rendering, complete with dedicated pages for post listings and individual articles.
  • Enhanced Environment Variable Handling: The environment variable setup has been refactored for improved type safety and clarity, including updates to env.d.ts and src/env.ts.
  • Updated Sentry Configuration: Sentry integration has been refined with updated DSN, organization, and project details, and the removal of the tunnel option for client-side monitoring.
  • New Legal and Contact Pages: Dedicated pages for Privacy Protocol and Terms of Service have been introduced, alongside a refactored contact page featuring a new form with zod validation.
  • Sitemap and Manifest Generation: The sitemap is now dynamically generated, replacing the static XML file, and the web manifest has been updated to reflect the new blog structure.
  • UI/UX and Accessibility Improvements: Various components across the marketing site have received updates for better user experience, including hash-based scrolling, dynamic content in hero sections, and enhanced accessibility attributes.
  • Dependency Management and Testing: The bun.lock and package.json files have been updated to reflect new and upgraded dependencies, and new test files have been added for various utilities and SEO metadata.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@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: 13

Note

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
src/app/[locale]/loading.tsx (1)

6-13: ⚠️ Potential issue | 🟠 Major

Localize hardcoded loading copy in this locale route.

Strings are currently hardcoded in English inside src/app/[locale]/..., including screen-reader text. This will render English UI in non-English locales.

Suggested direction
-const bootSequence = [
-  "INITIATING KINETIC COMMAND...",
-  ...
-  "[OK] SYSTEM READY."
-];
+// Pull localized strings from your locale dictionary/messages source
+const bootSequence = messages.loading.bootSequence;

-aria-label="Application loading"
+aria-label={messages.loading.ariaLabel}

-<span>Boot Sequence</span>
+<span>{messages.loading.bootSequenceLabel}</span>

Also applies to: 56-57, 86-87

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

In `@src/app/`[locale]/loading.tsx around lines 6 - 13, The bootSequence array and
other hardcoded strings in src/app/[locale]/loading.tsx (e.g., the bootSequence
constant and the screen-reader text at the other occurrences noted) must be
replaced with localized strings; extract each hardcoded message into your
i18n/message catalog (or use the existing getDictionary/getTranslator for this
route), then replace bootSequence with an array of translated entries (e.g.,
t('loading.boot.init') etc.) and replace any aria/live or screen-reader text
with translator calls as well; ensure you load the correct locale (using the
existing locale route helper) and fallback safely if a key is missing.
src/app/global-error.tsx (1)

1-10: ⚠️ Potential issue | 🟡 Minor

Missing Apache-2.0 license header.

As per coding guidelines, all source files must include the Apache-2.0 license header.

📄 Add license header
 "use client";

+// Copyright 2025 ResQ Software, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
 /**
  * Renders the application-wide error boundary when the root layout fails.
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/global-error.tsx` around lines 1 - 10, This file (global-error.tsx)
is missing the required Apache-2.0 license header; add the standard Apache-2.0
license comment block at the very top of the file (above the "use client"
directive) so it precedes all code and imports (e.g., the "use client" line and
imports of Sentry, useEffect, Button) to comply with project licensing
guidelines.
src/constants/app.ts (1)

1-6: ⚠️ Potential issue | 🟡 Minor

Missing Apache-2.0 license header.

Per coding guidelines, all source files must include the Apache-2.0 license header.

Proposed fix
+// Copyright 2025 ResQ Software, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
 /**
  * Declares static application metadata that is safe to embed across server and
  * client code.
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/constants/app.ts` around lines 1 - 6, This file is missing the required
Apache-2.0 license header; add the standard Apache-2.0 license header comment
block at the very top of src/constants/app.ts (above the existing file comment
and the import of packageJson) so the file begins with the full license header
before the current module-level comment and the import statement; ensure the
header is the canonical Apache-2.0 text used across the repo.
src/app/sitemap.ts (1)

1-6: ⚠️ Potential issue | 🟡 Minor

Missing Apache-2.0 license header.

Per coding guidelines, all source files must include the Apache-2.0 license header. Add the standard header at the top of the file.

Proposed fix
+// Copyright 2025 ResQ Software, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
 /**
  * Generates the static sitemap metadata for the marketing routes.
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/sitemap.ts` around lines 1 - 6, Add the standard Apache-2.0 license
header as the first block comment in this file so every source file includes the
required license; place it above the existing file comment and imports in
src/app/sitemap.ts (i.e., before the /** Generates the static sitemap
metadata... */ comment), ensuring the header is the canonical Apache-2.0 text
and preserves the existing imports of MetadataRoute and config.
src/config/config.ts (1)

1-6: ⚠️ Potential issue | 🟡 Minor

Missing required Apache-2.0 license header.

Per coding guidelines, all source files in src/**/*.{ts,tsx} must include the Apache-2.0 license header. Add the license comment at the top of the file.

Proposed fix
+// Copyright 2025 ResQ Software, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
 /**
  * Derives runtime-safe application configuration from static constants and the
  * validated environment layer.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/config/config.ts` around lines 1 - 6, Add the required Apache-2.0 license
header comment to the very top of this source file (above the existing JSDoc and
imports) so the file complies with project guidelines; ensure the standard
Apache-2.0 text block is used and placed before the import lines that reference
symbols like app and env (i.e., before the lines "import { app } from
"@/constants/app";" and "import { env } from "@/env";").
src/utils/seo/meta.ts (1)

1-3: ⚠️ Potential issue | 🟡 Minor

Missing required Apache-2.0 license header.

Per coding guidelines, all source files in src/**/*.{ts,tsx} must include the Apache-2.0 license header.

Proposed fix
+// Copyright 2025 ResQ Software, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
 import { app } from '@/constants';
 import type { Metadata, Viewport } from 'next';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/seo/meta.ts` around lines 1 - 3, The file src/utils/seo/meta.ts is
missing the required Apache-2.0 license header; add the standard Apache-2.0
license comment block at the very top of the file (before any imports) so all
source files under src/**/*.{ts,tsx} comply; ensure the header includes the
copyright line and Apache-2.0 notice and leave the rest of the file (imports
like app, and types Metadata and Viewport) unchanged.
src/features/marketing/components/navbar.tsx (2)

172-186: ⚠️ Potential issue | 🟡 Minor

Replace overflow-visible! with !overflow-visible in two locations (lines 172 and 182).

Tailwind CSS v4 uses prefix-based important modifiers, so overflow-visible! is ignored. Use !overflow-visible instead.

🔧 Proposed fix
-                <NavigationMenuContent className="overflow-visible!">
+                <NavigationMenuContent className="!overflow-visible">
@@
-                <NavigationMenuContent className="overflow-visible!">
+                <NavigationMenuContent className="!overflow-visible">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/marketing/components/navbar.tsx` around lines 172 - 186, In
NavigationMenuContent components inside the navbar (the two occurrences
currently using className="overflow-visible!"), replace the invalid Tailwind v4
suffix-style modifier with the prefix-style important modifier by changing those
className values to use "!overflow-visible" instead of "overflow-visible!";
locate the two NavigationMenuContent usages near the
NavigationMenuItem/NavigationMenuTrigger blocks (the ones around platformItems
mapping and the developers section) and update their className strings
accordingly.

66-90: ⚠️ Potential issue | 🟠 Major

Use the localized Link wrapper for internal routes in NavListItem.

NavListItem always renders a raw <a> tag, causing internal routes (like /platform, /protocol) to bypass Next.js client-side navigation and prefetching. This also risks bypassing locale-aware routing provided by next-intl.

For internal routes, import and use the localized Link from @/i18n/navigation. Keep raw <a> tags only for external targets:

import { Link } from "@/i18n/navigation";

Then conditionally render the appropriate element based on the external prop.

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

In `@src/features/marketing/components/navbar.tsx` around lines 66 - 90,
NavListItem currently always renders a raw <a> which breaks Next.js client
navigation and locale-aware routing for internal routes; update NavListItem to
import the localized Link from "@/i18n/navigation" and conditionally render that
Link (for internal routes when external is false) instead of the raw <a>, while
keeping the existing linkProps and external branch to render a plain <a> for
external links; ensure you wrap the chosen element with NavigationMenuLink
asChild and pass href, className, linkProps, and the title/description children
unchanged.
🟡 Minor comments (11)
src/features/marketing/sections/platform-pillars.tsx-52-54 (1)

52-54: ⚠️ Potential issue | 🟡 Minor

Heading opacity may reduce contrast below WCAG AA threshold.

The heading text uses text-text-primary/60 (60% opacity), which significantly reduces contrast compared to full opacity. Depending on the --color-text-primary value against --color-bg-primary, this may fall below WCAG AA contrast requirements (4.5:1 for normal text, 3:1 for large text).

Consider verifying the computed contrast ratio meets accessibility standards, or use full opacity for the heading text while keeping the emphasis on the span only.

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

In `@src/features/marketing/sections/platform-pillars.tsx` around lines 52 - 54,
The heading element with id "platform-heading" uses the utility class
text-text-primary/60 which may lower contrast below WCAG AA; update the h2
(platform-heading) to use full text color (remove the /60 opacity) and apply
reduced opacity only to any decorative/emphasized span inside the heading (or
adjust to a safer token like text-text-primary or text-text-primary/90) so the
main heading text retains sufficient contrast against the background; ensure you
change the className on the <h2 id="platform-heading"> and move any opacity
class to the specific <span> used for emphasis.
src/hooks/index.ts-1-1 (1)

1-1: ⚠️ Potential issue | 🟡 Minor

Missing Apache-2.0 license header.

As per coding guidelines, all source files in src/**/*.{ts,tsx,js,jsx} must include the Apache-2.0 license header.

Proposed fix
+// Copyright 2025 ResQ Software, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
 export * from "./use-in-view";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/index.ts` at line 1, Add the standard Apache-2.0 license header to
this source file so it complies with project guidelines; update the top of
src/hooks/index.ts (above the existing export * from "./use-in-view"; line) to
include the full Apache-2.0 license comment block used across the repo so all
files matching src/**/*.{ts,tsx,js,jsx} have the required header.
src/utils/helpers.ts-117-136 (1)

117-136: ⚠️ Potential issue | 🟡 Minor

Don't force smooth scrolling for users who requested reduced motion.

globals.css only enables smooth scrolling when the user has not asked for reduced motion, but this helper hard-codes "smooth" and overrides that preference for every programmatic scroll.

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

In `@src/utils/helpers.ts` around lines 117 - 136, The helper
scrollElementIntoCenterView currently hardcodes smooth scrolling; change it to
respect user reduced-motion preferences by removing the hard-coded default and
computing an effective behavior: detect prefers-reduced-motion via
window.matchMedia('(prefers-reduced-motion: reduce)').matches (guard for typeof
window === 'undefined' for SSR), then set effectiveBehavior = behavior ??
(prefersReduced ? 'auto' : 'smooth'), and pass effectiveBehavior to
window.scrollTo; update the function signature (scrollElementIntoCenterView) to
not force "smooth" and use this computed behavior.
src/styles/globals.css-67-83 (1)

67-83: ⚠️ Potential issue | 🟡 Minor

Quote SFMono-Regular in the new locale font stacks.

The added unquoted token is already tripping Stylelint's value-keyword-case rule on Lines 70, 76, and 82. Quoting the family name fixes the new lint errors.

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

In `@src/styles/globals.css` around lines 67 - 83, The font-family stacks in the
locale-specific :root rules (:root[lang="zh"], :root[lang="hi"],
:root[lang="ar"]) use SFMono-Regular unquoted in the --font-data values which
triggers Stylelint's value-keyword-case; update each --font-data declaration to
wrap SFMono-Regular in quotes (e.g., "SFMono-Regular") so the family name is a
valid quoted font-family token while keeping the other fallbacks unchanged.
src/utils/helpers.test.ts-148-163 (1)

148-163: ⚠️ Potential issue | 🟡 Minor

Missing cleanup for NEXT_PUBLIC_BASE_URL in the finally block.

Line 157 deletes NEXT_PUBLIC_BASE_URL inside the try block, but if an assertion fails before that line, the env variable remains set, potentially polluting subsequent tests.

🛡️ Proposed fix to move cleanup to finally block
 test("uses env variable as fallback", () => {
   const original = globalThis.location;
+  const savedEnv = process.env.NEXT_PUBLIC_BASE_URL;
   try {
     // `@ts-expect-error` - testing without location
     delete globalThis.location;
     process.env.NEXT_PUBLIC_BASE_URL = "https://env.example.com";

     expect(getURL("docs")).toBe("https://env.example.com/docs");
-
-    delete process.env.NEXT_PUBLIC_BASE_URL;
   } finally {
+    if (savedEnv !== undefined) {
+      process.env.NEXT_PUBLIC_BASE_URL = savedEnv;
+    } else {
+      delete process.env.NEXT_PUBLIC_BASE_URL;
+    }
     if (original) {
       globalThis.location = original;
     }
   }
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/helpers.test.ts` around lines 148 - 163, The test for getURL
mutates process.env.NEXT_PUBLIC_BASE_URL inside the try block and deletes it
there, which can leave the env var set if an assertion throws; capture the
original value (e.g., const originalBase = process.env.NEXT_PUBLIC_BASE_URL)
before mutating, then move cleanup into the finally block to restore
process.env.NEXT_PUBLIC_BASE_URL to originalBase (or delete it if originalBase
was undefined); keep the existing globalThis.location restore logic and ensure
both environment and location are always restored in finally.
src/app/[locale]/error.tsx-1-5 (1)

1-5: ⚠️ Potential issue | 🟡 Minor

Missing Apache-2.0 license header.

This file is missing the required license header. Additionally, the Button import is placed awkwardly before the JSDoc comment block.

🔧 Add license header and reorganize imports
 "use client";
 
-
-import { Button } from "@resq-sw/ui/button";
+// Copyright 2025 ResQ Software, Inc.
+// SPDX-License-Identifier: Apache-2.0
+
 /**
  * Renders the route-level error boundary for recoverable application failures.
  */
 import * as Sentry from "@sentry/nextjs";
 import { useEffect } from "react";
+import { Button } from "@resq-sw/ui/button";

As per coding guidelines: "All source files must include the Apache-2.0 license header."

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

In `@src/app/`[locale]/error.tsx around lines 1 - 5, Add the required Apache-2.0
license header at the top of the file and ensure the "use client" directive and
imports are placed after the header and any file-level JSDoc; specifically,
insert the standard Apache-2.0 license block above the "use client" line, then
move the Button import (import { Button } from "@resq-sw/ui/button") to follow
the header and JSDoc so the file-level comment and license appear first.
src/app/[locale]/(marketing)/layout.tsx-16-21 (1)

16-21: ⚠️ Potential issue | 🟡 Minor

Skip link text should use i18n translation.

The "Skip to main content" text is hardcoded in English within a locale-aware route. Since this layout is under [locale], it should use the translated string from the message catalogs (e.g., Nav.skipToContent).

Proposed fix using next-intl
+import { useTranslations } from "next-intl";
+
 export default function MarketingLayout({
   children,
 }:  Readonly<{
   children: React.ReactNode;
 }>) {
+  const t = useTranslations("Nav");
   return (
     <>
       <HashScrollCenter />
       <a
         href="#main-content"
         className="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-100 focus:rounded-md focus:bg-bg-card focus:px-4 focus:py-2 focus:text-text-primary focus:outline-2 focus:outline-accent-blue"
       >
-        Skip to main content
+        {t("skipToContent")}
       </a>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[locale]/(marketing)/layout.tsx around lines 16 - 21, The skip link
text is hardcoded; update the anchor in layout.tsx to use the i18n message key
(e.g., Nav.skipToContent) by importing and using next-intl's translations hook
(useTranslations) or equivalent, e.g., call const t = useTranslations('Nav') in
the layout component and replace the literal "Skip to main content" with
t('skipToContent') (or the exact key used in your messages), ensuring the
translated string is rendered for the current locale.
src/features/marketing/components/contact-form.tsx-26-31 (1)

26-31: ⚠️ Potential issue | 🟡 Minor

Missing error handling for submission failures.

The form only handles the success case (result?.data?.success). If the server action fails or returns an error, the user receives no feedback. Consider displaying an error message when the submission fails.

Proposed fix
+  const [error, setError] = useState<string | null>(null);
+
   const form = useForm({
     defaultValues: {
       name: "",
       email: "",
       organization: "",
       message: "",
     },
     onSubmit: async ({ value }) => {
+      setError(null);
       const result = await executeAsync(value);
       if (result?.data?.success) {
         setSubmitted(true);
+      } else {
+        setError(result?.data?.message ?? "Submission failed. Please try again.");
       }
     },
   });

Then render the error above the submit button:

{error && (
  <p role="alert" className="font-mono text-xs text-accent-red">
    {error}
  </p>
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/marketing/components/contact-form.tsx` around lines 26 - 31, The
onSubmit handler (onSubmit, executeAsync, setSubmitted) only handles the success
path and ignores failures or thrown exceptions; update onSubmit to catch errors
and handle non-success responses by setting an error state (e.g., error via
useState) and clearing it on success, and ensure thrown exceptions from
executeAsync are caught (try/catch) to set that error state; finally render the
error state above the submit button (the suggested <p role="alert"
...>{error}</p> block) so users see failure feedback.
src/app/[locale]/(marketing)/blog/page.tsx-17-23 (1)

17-23: ⚠️ Potential issue | 🟡 Minor

Hardcoded locale breaks i18n date formatting.

The formatDate function hardcodes "en-US" locale, but the app supports multiple locales (English, Spanish, Chinese, Hindi, Arabic). Users viewing the blog in other locales will still see English-formatted dates.

Consider using the page's locale parameter for proper localization:

💡 Proposed fix to use dynamic locale
-function formatDate(dateStr: string) {
-  return new Date(dateStr).toLocaleDateString("en-US", {
+function formatDate(dateStr: string, locale: string = "en-US") {
+  return new Date(dateStr).toLocaleDateString(locale, {
     year: "numeric",
     month: "long",
     day: "numeric",
   });
 }

Then pass the locale from page params:

export default function BlogPage({ params }: { params: { locale: string } }) {
  // ...
  {formatDate(post.date, params.locale)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[locale]/(marketing)/blog/page.tsx around lines 17 - 23, The
formatDate function currently hardcodes "en-US" which breaks i18n; update
formatDate(dateStr: string, locale?: string) to accept a locale parameter and
pass that locale to toLocaleDateString instead of the fixed "en-US", then update
BlogPage (export default function BlogPage({ params }: { params: { locale:
string } })) to call formatDate(post.date, params.locale) when rendering posts
so dates are localized per the page locale.
src/features/marketing/sections/footer-cta.tsx-46-84 (1)

46-84: ⚠️ Potential issue | 🟡 Minor

The "256-BIT SSL" claim is misleading for a mailto: form.

The form uses action="mailto:..." which simply opens the user's email client rather than submitting data over HTTPS. The "ENCRYPTED TRANSMISSION // 256-BIT SSL" text on line 83 is inaccurate since no server-side encrypted transmission occurs—the email client handles delivery using whatever transport the user's email provider uses.

Consider either:

  1. Removing or rewording the encryption claim to avoid misleading users
  2. Implementing an actual server-side form submission (via a server action) if encrypted transmission is a requirement
💡 Suggested text change
-          ENCRYPTED TRANSMISSION {"// "}256-BIT SSL
+          DIRECT EMAIL {"// "}OPENS MAIL CLIENT
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/marketing/sections/footer-cta.tsx` around lines 46 - 84, The
footer form currently uses action="mailto:..." (the <form> element with id/name
around the input id="footer-email" and submit <button>), so the adjacent
paragraph that reads "ENCRYPTED TRANSMISSION // 256-BIT SSL" is misleading;
either remove or reword that <p> (the string "ENCRYPTED TRANSMISSION // 256-BIT
SSL") to accurately reflect that the form opens the user’s email client (e.g.,
"Opens your email client; delivery handled by your email provider") or replace
the mailto form with a real server-side submission (implement a server
action/POST handler and update the <form> action to point to it) if you require
true HTTPS/encrypted transport.
src/features/marketing/components/footer.tsx-28-28 (1)

28-28: ⚠️ Potential issue | 🟡 Minor

Localize the remaining assistive text in the footer.

The visible copy is translated, but the nav label, homepage/social aria-labels, the external-link sr-only suffix, and toLocaleTimeString() are still English/browser-locale only. That leaves screen-reader output and timestamps inconsistent on non-English pages.

Also applies to: 91-109, 173-179

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

In `@src/features/marketing/components/footer.tsx` at line 28, Localize the
remaining assistive text and timestamps in the footer by replacing hard-coded
English strings and default locale calls: use the i18n translation function
(e.g., t or translate) for the nav label and all aria-labels for homepage/social
links and the external-link screen-reader suffix (the bit that renders when
external is true), and call toLocaleTimeString with the page locale (e.g.,
toLocaleTimeString(locale, options)) or format the timestamp via the app's date
helper so timestamps respect the current locale; update the components/variables
that render external, nav label, homepage/social aria-labels, and the
toLocaleTimeString usage accordingly (refer to the external conditional
rendering, nav aria-label props, and the timestamp rendering code).
🧹 Nitpick comments (13)
src/app/[locale]/loading.tsx (1)

3-4: Use cn() for conditional class composition.

Inline ternary class selection should use the shared cn() utility for consistency with project styling conventions.

Suggested change
 import { AnimatePresence, motion, useReducedMotion } from 'motion/react';
 import { useEffect, useState } from 'react';
+import { cn } from '@/lib/utils';
 ...
- className={index === bootSequence.length - 1 ? 'font-bold text-accent-green' : 'text-accent-red'}
+ className={cn(
+   index === bootSequence.length - 1 ? 'font-bold text-accent-green' : 'text-accent-red'
+ )}

As per coding guidelines, src/{app,components,features,styles}/**/*.{ts,tsx,css}: Styling must use Tailwind CSS v4 with configuration in src/styles/globals.css; use cn() utility from src/lib/utils for conditional class composition.

Also applies to: 70-70

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

In `@src/app/`[locale]/loading.tsx around lines 3 - 4, The component in
src/app/[locale]/loading.tsx uses inline ternary expressions for className
composition; import the shared cn utility (from src/lib/utils) and replace those
inline ternaries (including the one around line 70) with cn(...) calls so
classes are composed consistently — update the imports to include cn, then
change className={condition ? 'a b' : 'c d'} to className={cn('base-classes',
condition && 'conditional-class')} or the object-style usage supported by cn to
preserve all original classes and conditional logic.
package.json (1)

66-66: @types/bun should be in devDependencies.

Type definition packages like @types/bun are only needed during development/build time and should not be bundled with production dependencies.

Proposed fix

Move @types/bun from dependencies to devDependencies:

 "devDependencies": {
   "@biomejs/biome": "2.4.8",
   "@tailwindcss/postcss": "^4.2.1",
   "@tailwindcss/typography": "^0.5.19",
+  "@types/bun": "^1.3.11",
   "@types/node": "^25.4.0",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 66, The dependency entry "@types/bun" currently listed
under dependencies should be moved to devDependencies in package.json; update
the package.json by removing the "@types/bun" entry from the dependencies object
and adding the same version string to the devDependencies object, then
reinstall/update lockfiles (npm/yarn) to reflect the change so production
installs no longer include this dev-only type package.
src/features/marketing/sections/use-cases.tsx (1)

134-136: Avoid split(" ") for mobile tab labels; it’s locale-fragile.

Word-splitting assumes whitespace tokenization and can produce poor labels across localized content. Prefer explicit per-locale shortLabel values.

💡 Minimal refactor
 const USE_CASES = [
   {
     id: "disaster",
     label: "Disaster Response",
+    shortLabel: "Disaster",
@@
-                  <span className="sm:hidden">
-                    {useCase.label.split(" ")[0]}
-                  </span>
+                  <span className="sm:hidden">
+                    {useCase.shortLabel ?? useCase.label}
+                  </span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/marketing/sections/use-cases.tsx` around lines 134 - 136,
Replace the fragile split logic by using an explicit short label field for
mobile: stop calling useCase.label.split(" ")[0] in the rendering and instead
render useCase.shortLabel (fallback to useCase.label if shortLabel is missing);
update the useCases data objects (where the array of useCase entries is defined)
to include a per-locale shortLabel for each entry so mobile labels are stable
and localization-safe (adjust the component that maps/renders use cases—the
useCase variable in this file/component—so it reads shortLabel when present).
src/utils/helpers.test.ts (1)

117-135: Environment cleanup may leave residual state if variables were previously undefined.

Object.assign(process.env, saved) restores values but doesn't re-delete keys that were removed during the test if they weren't originally present. Consider using a more robust cleanup pattern.

♻️ Suggested safer cleanup pattern
 test("returns empty string when no origin and no env vars", () => {
   const original = globalThis.location;
   try {
     // `@ts-expect-error` - testing without location
     delete globalThis.location;
-    const saved = { ...process.env };
+    const savedVite = process.env.VITE_BASE_URL;
+    const savedNext = process.env.NEXT_PUBLIC_BASE_URL;
+    const savedBase = process.env.BASE_URL;
     delete process.env.VITE_BASE_URL;
     delete process.env.NEXT_PUBLIC_BASE_URL;
     delete process.env.BASE_URL;

     expect(getURL()).toBe("");

-    Object.assign(process.env, saved);
+    if (savedVite !== undefined) process.env.VITE_BASE_URL = savedVite;
+    if (savedNext !== undefined) process.env.NEXT_PUBLIC_BASE_URL = savedNext;
+    if (savedBase !== undefined) process.env.BASE_URL = savedBase;
   } finally {
     if (original) {
       globalThis.location = original;
     }
   }
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/helpers.test.ts` around lines 117 - 135, The test for getURL
mutates process.env but only calls Object.assign(process.env, saved) which
leaves keys created during the test if they weren't present originally; update
the cleanup to restore environment exactly by capturing the saved env (const
saved = { ...process.env }) then in the finally block first remove any keys that
are present now but not in saved (iterate Object.keys(process.env) and delete
keys not in saved), then Object.assign(process.env, saved); keep the existing
globalThis.location restore logic as-is so location is reattached in finally.
src/app/[locale]/not-found.tsx (1)

21-28: Consider adding NotFound translations to maintain consistency with the i18n infrastructure.

The project uses next-intl for internationalization with message files for multiple locales (en, es, ar, zh, hi), but the messages do not currently include NotFound/404 keys. While this page displays hardcoded English text under the [locale] route, this pattern is consistent with other locale-specific pages in the codebase (e.g., error.tsx, contact/page.tsx) that also use hardcoded English rather than translations. If translating error pages becomes a priority, you'll need to add NotFound keys to all message files and use getTranslations() in the Server Component.

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

In `@src/app/`[locale]/not-found.tsx around lines 21 - 28, Add locale-aware
messages for the NotFound page and replace the hardcoded English strings with
translations: add NotFound.title and NotFound.description (or similarly named
keys) to each locale messages file (en, es, ar, zh, hi), then in
src/app/[locale]/not-found.tsx call next-intl's getTranslations (e.g., const t =
getTranslations('NotFound')) in the server component and replace the h1 and
paragraph content with t('title') and t('description') respectively so the page
renders localized text for each locale.
src/app/manifest.ts (2)

99-99: Inconsistent branding: short_name is "MO".

The short_name property is set to "MO" which appears to be leftover from previous branding. Consider updating to align with ResQ branding (e.g., "ResQ").

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

In `@src/app/manifest.ts` at line 99, Update the app manifest's short_name value
from the leftover "MO" to the ResQ branding (e.g., "ResQ") in the manifest
object where short_name is defined so it matches other branding assets; locate
the short_name entry in src/app/manifest.ts and replace "MO" with the desired
short name (e.g., "ResQ") ensuring casing matches other identifiers.

1-2: License header should be at the top of the file.

The Apache-2.0 license header is placed after the import statement. As per coding guidelines, the license header should be at the very beginning of the file.

♻️ Move license header to top
+/**
+ * Copyright 2025 ResQ Systems
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ ...
+ */
+
 import type { MetadataRoute } from 'next/types';
-
-/**
- * Copyright 2025 ResQ Systems
- ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/manifest.ts` around lines 1 - 2, Move the Apache-2.0 license header
to the very top of the file so it appears before any code or imports;
specifically, place the license comment block above the existing import line
(the import type { MetadataRoute } from 'next/types'; in manifest.ts) ensuring
the license is the first content in the file.
src/app/[locale]/(marketing)/protocol/page.tsx (2)

13-17: Metadata should also be localized.

The metadata export uses hardcoded English strings. For proper SEO across all locales, consider using generateMetadata with locale-aware titles and descriptions.

Example using generateMetadata
-export const metadata = constructMetadata({
-  title: "Protocol",
-  description:
-    "The ResQ protocol is designed to operate when everything else has failed — a decentralized protocol built for zero-connectivity, GPS-denied, adversarial environments.",
-});
+import { getTranslations } from "next-intl/server";
+
+export async function generateMetadata({ params }: { params: { locale: string } }) {
+  const t = await getTranslations({ locale: params.locale, namespace: "Protocol" });
+  return constructMetadata({
+    title: t("label"),
+    description: t("description"),
+  });
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[locale]/(marketing)/protocol/page.tsx around lines 13 - 17, The
current export const metadata = constructMetadata(...) uses hardcoded English
strings; replace it with a locale-aware export async function generateMetadata({
params }) (or the Next.js generateMetadata signature used in this app) that
returns the same constructMetadata(...) call but passes in title and description
looked up from your localization resource (e.g., translations object or
i18n/getTranslator) based on params.locale; update references to
constructMetadata, metadata and page.tsx accordingly so metadata is generated
per-locale instead of hardcoded.

23-36: Hero section text should use i18n translations.

The hero section has hardcoded English text, but this page is within a locale-aware route ([locale]). The text should use translations from Protocol.* keys in the message catalogs for proper internationalization.

Proposed fix using next-intl
+import { useTranslations } from "next-intl";
 import { constructMetadata } from "@/utils/seo/meta";
 // ...

 export default function ProtocolPage() {
+  const t = useTranslations("Protocol");
   return (
     <>
       {/* Inline hero header */}
       <section aria-labelledby="protocol-hero-heading" className="bg-bg-primary pt-32 pb-16 md:pt-40 md:pb-20">
         <div className="container">
           <p className="mb-6 font-mono text-xs uppercase tracking-[0.2em] text-accent-red">
-            The Protocol
+            {t("label")}
           </p>
           <h1 id="protocol-hero-heading" className="max-w-4xl font-display text-4xl font-extrabold tracking-tight text-text-primary md:text-5xl lg:text-6xl">
-            Built for Zero-Connectivity, GPS-Denied, Adversarial Environments
+            {t("heading")}
           </h1>
           <p className="mt-6 max-w-2xl text-lg text-text-secondary">
-            The architecture assumption is inverted: ResQ is designed to operate
-            when everything else has failed, not when everything is working.
+            {t("description")}
           </p>
         </div>
       </section>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[locale]/(marketing)/protocol/page.tsx around lines 23 - 36, Replace
the hardcoded hero texts with next-intl translations: import and call
useTranslations('Protocol') (e.g., const t = useTranslations('Protocol')) in the
page component, then replace the kicker "The Protocol", the h1 title "Built for
Zero-Connectivity, GPS-Denied, Adversarial Environments" and the paragraph text
with t('hero.kicker'), t('hero.title') and t('hero.description') (or equivalent
Protocol.* keys from the message catalog), preserving the existing
aria-labelledby/id and classNames so layout and accessibility remain unchanged.
src/features/marketing/components/contact-form.tsx (2)

59-64: Consider extracting duplicated validation logic.

The validation logic for each field is duplicated between onBlur and onSubmit handlers. TanStack Form supports sharing validators to reduce duplication.

Example refactor
+const nameValidator = ({ value }: { value: string }) =>
+  !value ? "Name is required" : value.length > 200 ? "Name is too long" : undefined;
+
 <form.Field
   name="name"
   validators={{
-    onBlur: ({ value }) =>
-      !value ? "Name is required" : value.length > 200 ? "Name is too long" : undefined,
-    onSubmit: ({ value }) =>
-      !value ? "Name is required" : value.length > 200 ? "Name is too long" : undefined,
+    onBlur: nameValidator,
+    onSubmit: nameValidator,
   }}
 >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/marketing/components/contact-form.tsx` around lines 59 - 64, The
validators for the Name field duplicate identical logic in the validators object
(onBlur and onSubmit); extract a shared validator function (e.g., const
validateName = ({ value }) => !value ? "Name is required" : value.length > 200 ?
"Name is too long" : undefined) and replace both validators.onBlur and
validators.onSubmit with references to that function so the same logic is
reused; apply the same pattern for other fields if present and ensure the new
function is imported/defined in the same scope as the contact form component.

34-45: Hardcoded UI strings should use i18n translations.

The success message and all form labels/placeholders are hardcoded in English. Since the app supports 5 locales, these should use the translated strings from Contact.form.* in the message catalogs.

Example fix for success message
+"use client";
+
+import { useTranslations } from "next-intl";
 import { useForm } from "@tanstack/react-form";
 // ...

 export function ContactForm() {
+  const t = useTranslations("Contact.form");
   const [submitted, setSubmitted] = useState(false);
   // ...

   if (submitted) {
     return (
       <div role="status" className="flex flex-col items-center justify-center gap-4 py-16 text-center">
         <p className="font-mono text-xs font-bold uppercase tracking-widest text-accent-green">
-          Transmission Received
+          {t("successTitle")}
         </p>
         <p className="max-w-sm text-sm text-text-secondary">
-          We&apos;ll be in touch shortly. Thank you for reaching out.
+          {t("successBody")}
         </p>
       </div>
     );
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/marketing/components/contact-form.tsx` around lines 34 - 45, The
success UI block guarded by the `submitted` check currently uses hardcoded
English strings; replace the static texts in that return (e.g., "Transmission
Received" and "We'll be in touch shortly. Thank you for reaching out.") with
i18n lookups using the project's message catalog keys (e.g.,
Contact.form.successTitle and Contact.form.successBody) via the existing
translation hook/function used in this component (refer to where the component
imports/uses the translator). Also update all other hardcoded
labels/placeholders in this component (form labels, button text, placeholders)
to use the corresponding Contact.form.* keys so the component supports all
locales.
messages/ar.json (1)

278-278: Consider using a localized email example for Arabic locale.

The email placeholder uses jane@example.com while the name placeholder correctly uses an Arabic name (محمد أحمد). For consistency, consider using an Arabic-region example like mohammed@example.com or similar.

Suggested change
-      "emailPlaceholder": "jane@example.com",
+      "emailPlaceholder": "mohammed@example.com",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@messages/ar.json` at line 278, Update the "emailPlaceholder" entry to use a
localized Arabic-region example instead of "jane@example.com"; locate the
"emailPlaceholder" key in the messages/ar.json translations and replace its
value with an Arabic-friendly example such as "mohammed@example.com" (or another
Arabic-script/localized address) to match the existing Arabic "namePlaceholder"
localization.
src/app/[locale]/layout.tsx (1)

97-97: Use cn() for the <html> class composition.

localeFont is optional here, and the repo standard is to route conditional Tailwind class composition through src/lib/utils.

♻️ Proposed refactor
+import { cn } from "@/lib/utils";
@@
-      className={`dark ${syne.variable} ${dmSans.variable} ${dmMono.variable} ${localeFont}`}
+      className={cn(
+        "dark",
+        syne.variable,
+        dmSans.variable,
+        dmMono.variable,
+        localeFont,
+      )}

As per coding guidelines, "Styling must use Tailwind CSS v4 with configuration in src/styles/globals.css; use cn() utility from src/lib/utils for conditional class composition".

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

In `@src/app/`[locale]/layout.tsx at line 97, Replace the template literal
className on the <html> element with the cn() utility and import it from
src/lib/utils; specifically, add an import for cn and change className={`dark
${syne.variable} ${dmSans.variable} ${dmMono.variable} ${localeFont}`} to
className={cn('dark', syne.variable, dmSans.variable, dmMono.variable,
localeFont)} so the optional localeFont is handled correctly and class
composition follows the repo standard.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2310f610-b5f8-4589-8416-a7304d85926c

📥 Commits

Reviewing files that changed from the base of the PR and between b5f552e and a5721b9.

⛔ Files ignored due to path filters (9)
  • bun.lock is excluded by !**/*.lock
  • public/assets/images/active-fires.jpeg is excluded by !**/*.jpeg
  • public/assets/images/car-crashes.jpeg is excluded by !**/*.jpeg
  • public/assets/images/mass-casualty-incident.jpeg is excluded by !**/*.jpeg
  • public/assets/images/natural-disasters.jpeg is excluded by !**/*.jpeg
  • public/assets/images/swat-and-technical.jpeg is excluded by !**/*.jpeg
  • public/assets/images/traffic-violation.jpeg is excluded by !**/*.jpeg
  • public/assets/videos/Futuristic_Drone_Lands_On_Logo.mp4 is excluded by !**/*.mp4
  • public/assets/videos/drone-landing-video.mp4 is excluded by !**/*.mp4
📒 Files selected for processing (81)
  • .gitignore
  • content/posts/building-for-zero-connectivity.mdx
  • content/posts/decentralized-mesh-networks-save-lives.mdx
  • content/posts/introducing-resq-tactical-os.mdx
  • env.d.ts
  • instrumentation-client.ts
  • messages/ar.json
  • messages/en.json
  • messages/es.json
  • messages/hi.json
  • messages/zh.json
  • next.config.ts
  • package.json
  • public/assets/images/active-fires.webp
  • public/assets/images/car-crashes.webp
  • public/assets/images/mass-casualty-incident.webp
  • public/assets/images/natural-disasters.webp
  • public/assets/images/swat-and-technical.webp
  • public/assets/images/traffic-violation.webp
  • public/sitemap.xml
  • src/actions/contact/submit-contact.ts
  • src/actions/contact/submit.ts
  • src/app/(marketing)/layout.tsx
  • src/app/[locale]/(marketing)/blog/[slug]/page.tsx
  • src/app/[locale]/(marketing)/blog/page.tsx
  • src/app/[locale]/(marketing)/contact/page.tsx
  • src/app/[locale]/(marketing)/layout.tsx
  • src/app/[locale]/(marketing)/legal/layout.tsx
  • src/app/[locale]/(marketing)/legal/privacy/page.tsx
  • src/app/[locale]/(marketing)/legal/terms/page.tsx
  • src/app/[locale]/(marketing)/page.tsx
  • src/app/[locale]/(marketing)/platform/page.tsx
  • src/app/[locale]/(marketing)/protocol/page.tsx
  • src/app/[locale]/error.tsx
  • src/app/[locale]/forbidden.tsx
  • src/app/[locale]/layout.tsx
  • src/app/[locale]/loading.tsx
  • src/app/[locale]/not-found.tsx
  • src/app/api/[[...route]]/health/handlers.ts
  • src/app/global-error.tsx
  • src/app/layout.tsx
  • src/app/manifest.ts
  • src/app/sitemap.ts
  • src/config/config.ts
  • src/constants/app.ts
  • src/env.ts
  • src/features/marketing/components/contact-form.tsx
  • src/features/marketing/components/footer.tsx
  • src/features/marketing/components/hash-scroll-center.tsx
  • src/features/marketing/components/hero-hud.tsx
  • src/features/marketing/components/hero-video-slideshow.tsx
  • src/features/marketing/components/navbar.tsx
  • src/features/marketing/sections/architecture-diagram.tsx
  • src/features/marketing/sections/competitive-table.tsx
  • src/features/marketing/sections/footer-cta.tsx
  • src/features/marketing/sections/hero.tsx
  • src/features/marketing/sections/how-it-works.tsx
  • src/features/marketing/sections/index.ts
  • src/features/marketing/sections/metrics-strip.tsx
  • src/features/marketing/sections/platform-pillars.tsx
  • src/features/marketing/sections/problem-statement.tsx
  • src/features/marketing/sections/technology.tsx
  • src/features/marketing/sections/trust-security.tsx
  • src/features/marketing/sections/use-cases.tsx
  • src/features/marketing/sections/why-now.tsx
  • src/hooks/index.ts
  • src/i18n/navigation.ts
  • src/i18n/request.ts
  • src/i18n/routing.ts
  • src/lib/content.ts
  • src/lib/utils.test.ts
  • src/mdx-components.tsx
  • src/proxy.ts
  • src/styles/globals.css
  • src/utils/helpers.test.ts
  • src/utils/helpers.ts
  • src/utils/logger.test.ts
  • src/utils/seo/meta.test.ts
  • src/utils/seo/meta.ts
  • src/utils/seo/speculation-schema.test.ts
  • test.ts
💤 Files with no reviewable changes (4)
  • src/app/[locale]/forbidden.tsx
  • src/app/(marketing)/layout.tsx
  • public/sitemap.xml
  • src/actions/contact/submit-contact.ts

}: { params: Promise<{ slug: string }> }): Promise<Metadata> {
return params.then(({ slug }) => {
const post = getPostBySlug(slug);
if (!post) return constructMetadata({ title: "Post Not Found" });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This route is still pinned to English.

"Post Not Found", "Back to blog", and toLocaleDateString("en-US", …) all bypass the active locale, so non-English routes will still render mixed-language metadata and page chrome. Please source these strings from translations and format the date with the current locale.

Also applies to: 31-36, 52-57

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

In `@src/app/`[locale]/(marketing)/blog/[slug]/page.tsx at line 22, The page is
using hard-coded English strings and a fixed date locale; replace those with
localized translations and date formatting using the active locale: use your
existing i18n/dictionary lookup (e.g., getDictionary or t) to source the "Post
Not Found" and "Back to blog" strings instead of the literals used around
constructMetadata and in the page markup, and replace
toLocaleDateString("en-US", …) calls with Intl.DateTimeFormat(locale,
options).format(date) (where locale comes from the route/params or your locale
provider) so the metadata and page chrome (the constructMetadata call, post
title/summary rendering, and the back link) all respect the current locale.

Comment on lines +10 to +46
const TRUST_CARDS: ReadonlyArray<{
icon: ComponentType<{ className?: string; strokeWidth?: number }>;
title: string;
description: string;
}> = [
{
icon: Lock,
title: "AES-256 Encryption",
description:
"Military-grade encryption for all mesh communications. End-to-end by default, no exceptions.",
},
{
icon: Globe,
title: "Decentralized Architecture",
description:
"No central server to compromise. Every node is sovereign. The network survives any single point of failure.",
},
{
icon: Shield,
title: "Blockchain-Verified GPS",
description:
"Tamper-proof positioning backed by distributed ledger consensus. No spoofing, no drift.",
},
{
icon: Server,
title: "Edge-First Processing",
description:
"All computation happens on-device at the edge. Your data never leaves the mesh unless you authorize it.",
},
];

const COMPLIANCE_BADGES = [
"FIPS 140-2",
"SOC 2 Type II",
"CJIS Compliant",
"US Data Residency",
] as const;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Move Trust & Security copy to locale-aware content sources.

This section hardcodes all user-facing strings in English (cards, heading, badges), which breaks the multi-locale objective for localized marketing pages.

💡 Suggested direction (localized content injection)
-const TRUST_CARDS = [
+type TrustCard = Readonly<{
+  icon: ComponentType<{ className?: string; strokeWidth?: number }>;
+  title: string;
+  description: string;
+}>;

-const COMPLIANCE_BADGES = [
-  "FIPS 140-2",
-  "SOC 2 Type II",
-  "CJIS Compliant",
-  "US Data Residency",
-] as const;
-
-export function TrustSecurity() {
+export function TrustSecurity({
+  sectionLabel,
+  heading,
+  cards,
+  badges,
+}: Readonly<{
+  sectionLabel: string;
+  heading: string;
+  cards: ReadonlyArray<TrustCard>;
+  badges: ReadonlyArray<string>;
+}>) {
@@
-          <p className="mb-3 md:mb-4 font-mono text-xs uppercase tracking-[0.2em] text-accent-red">
-            08 {"//"} Trust & Security
-          </p>
+          <p className="mb-3 md:mb-4 font-mono text-xs uppercase tracking-[0.2em] text-accent-red">
+            {sectionLabel}
+          </p>
@@
-            Built for Zero-Trust Environments
+            {heading}
@@
-          {TRUST_CARDS.map((card, i) => {
+          {cards.map((card, i) => {
@@
-          {COMPLIANCE_BADGES.map((badge, i) => (
+          {badges.map((badge, i) => (

Also applies to: 68-73, 95-100, 115-121

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

In `@src/features/marketing/sections/trust-security.tsx` around lines 10 - 46, The
hardcoded English strings in TRUST_CARDS and COMPLIANCE_BADGES must be moved to
the app's localization layer and replaced with locale-aware lookups; update the
trust-security.tsx file to construct TRUST_CARDS and COMPLIANCE_BADGES using
your i18n/getText function (e.g., call t('trust.cards[0].title'),
t('trust.cards[0].description'), t('trust.badges[0]') or load a locale-aware
trustContent object) instead of literal strings, keep the same shapes/types
(icon: Lock/Globe/Shield/Server, title, description) so existing rendering code
that references TRUST_CARDS and COMPLIANCE_BADGES continues to work, and apply
the same change for the other hardcoded copies noted (the blocks around lines
referenced in the comment) so all user-facing copy is sourced from translations
or locale content providers.

Comment on lines +154 to +159
export const ScrollIntoCenterView = (href: string): boolean => {
if (typeof document === "undefined") {
return false;
}

return scrollElementIntoCenterView(document.querySelector(href));
Copy link
Copy Markdown

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:

# First, check the file at the specified lines
cat -n src/utils/helpers.ts | sed -n '150,165p'

Repository: resq-software/landing

Length of output: 415


🏁 Script executed:

# Find the scrollElementIntoCenterView function to understand what it does
rg -n "scrollElementIntoCenterView" --type ts --type tsx -B 2 -A 10

Repository: resq-software/landing

Length of output: 92


🏁 Script executed:

# Get overall context of the helpers.ts file
wc -l src/utils/helpers.ts

Repository: resq-software/landing

Length of output: 89


🏁 Script executed:

# Search for scrollElementIntoCenterView without file type restrictions
rg -n "scrollElementIntoCenterView" -B 2 -A 10

Repository: resq-software/landing

Length of output: 1290


🏁 Script executed:

# Also check the whole helpers.ts file to understand the pattern
head -n 160 src/utils/helpers.ts

Repository: resq-software/landing

Length of output: 4992


🏁 Script executed:

# Search for usages of ScrollIntoCenterView function
rg -n "ScrollIntoCenterView" -B 2 -A 3

Repository: resq-software/landing

Length of output: 346


🏁 Script executed:

# Check if there are tests that show expected usage
fd -e test.ts -e spec.ts -e test.tsx -e spec.tsx | head -20

Repository: resq-software/landing

Length of output: 205


🏁 Script executed:

# Check the test file for ScrollIntoCenterView
cat src/utils/helpers.test.ts

Repository: resq-software/landing

Length of output: 4707


Add error handling to prevent unhandled exceptions from document.querySelector().

document.querySelector() throws a DOMException when passed an invalid CSS selector. Since the function signature guarantees a boolean return type, invalid href values will break the function's contract. Wrap the querySelector call in a try-catch block to safely return false for any selector errors.

Proposed fix
 export const ScrollIntoCenterView = (href: string): boolean => {
   if (typeof document === "undefined") {
     return false;
   }
 
-  return scrollElementIntoCenterView(document.querySelector(href));
+  try {
+    return scrollElementIntoCenterView(document.querySelector(href));
+  } catch {
+    logger.warn(`ScrollIntoCenterView: invalid selector "${href}"`);
+    return false;
+  }
 };
📝 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
export const ScrollIntoCenterView = (href: string): boolean => {
if (typeof document === "undefined") {
return false;
}
return scrollElementIntoCenterView(document.querySelector(href));
export const ScrollIntoCenterView = (href: string): boolean => {
if (typeof document === "undefined") {
return false;
}
try {
return scrollElementIntoCenterView(document.querySelector(href));
} catch {
logger.warn(`ScrollIntoCenterView: invalid selector "${href}"`);
return false;
}
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/helpers.ts` around lines 154 - 159, ScrollIntoCenterView can throw
if document.querySelector receives an invalid selector; wrap the querySelector
call in a try-catch inside ScrollIntoCenterView and return false on any
exception, and also return false if querySelector returns null before calling
scrollElementIntoCenterView; reference the ScrollIntoCenterView function and the
scrollElementIntoCenterView call when applying this change.

Comment on lines +15 to +25
test("defaults to ALL level in non-production", () => {
const original = process.env.NODE_ENV;
process.env.NODE_ENV = "development";

const logger = new Logger("TestDev");
logger.trace("trace msg");
logger.debug("debug msg");
logger.info("info msg");

process.env.NODE_ENV = original;
});
Copy link
Copy Markdown

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:

cd /tmp && find . -name "logger.test.ts" 2>/dev/null | head -5

Repository: resq-software/landing

Length of output: 47


🏁 Script executed:

git ls-files | grep -i logger.test

Repository: resq-software/landing

Length of output: 89


🏁 Script executed:

find . -type f -name "*.test.ts" | head -10

Repository: resq-software/landing

Length of output: 215


🏁 Script executed:

git ls-files | grep -E "src/.*logger" | head -20

Repository: resq-software/landing

Length of output: 109


🏁 Script executed:

cat -n src/utils/logger.test.ts

Repository: resq-software/landing

Length of output: 8557


Use finally blocks or test lifecycle hooks to restore global state; await async rejection assertions.

Tests outside lines 54–132 restore process.env and console only on the success path—if a test throws, later tests inherit the modified state. Additionally, at lines 219–223, the rejection assertion lacks await or return, causing the test to pass without verifying the Promise rejection.

Move restoration into finally blocks or adopt beforeEach/afterEach hooks (as done in lines 54–85). For process.env.NODE_ENV, use delete process.env.NODE_ENV if the original value was undefined. For the async assertion, add await or return before expect().

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

In `@src/utils/logger.test.ts` around lines 15 - 25, Several tests (including the
one instantiating Logger("TestDev")) change process.env.NODE_ENV and console
state but only restore them on the success path; move restoration into finally
blocks or use test lifecycle hooks (beforeEach/afterEach) to ensure process.env
and console are restored even on failures, and when restoring NODE_ENV if the
saved original is undefined use delete process.env.NODE_ENV instead of assigning
undefined. Also update the async rejection assertion (the expect(...).rejects
usage around the promise at lines ~219–223) to use await or return (e.g., await
expect(...).rejects...) so the test actually waits for and verifies the Promise
rejection.

Comment on lines +219 to +223
expect(
logger.time("failing", () => {
throw new Error("boom");
}),
).rejects.toThrow("boom");
Copy link
Copy Markdown

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:

cd "$(git rev-parse --show-toplevel)" && pwd

Repository: resq-software/landing

Length of output: 83


🏁 Script executed:

git ls-files "src/utils/logger*" | head -20

Repository: resq-software/landing

Length of output: 109


🏁 Script executed:

wc -l src/utils/logger.test.ts

Repository: resq-software/landing

Length of output: 93


🏁 Script executed:

sed -n '200,230p' src/utils/logger.test.ts

Repository: resq-software/landing

Length of output: 809


🏁 Script executed:

sed -n '1,50p' src/utils/logger.ts

Repository: resq-software/landing

Length of output: 1654


🏁 Script executed:

grep -n "time(" src/utils/logger.ts | head -5

Repository: resq-software/landing

Length of output: 47


🏁 Script executed:

sed -n '100,180p' src/utils/logger.ts

Repository: resq-software/landing

Length of output: 2735


🏁 Script executed:

grep -A 20 "^\s*time(" src/utils/logger.ts

Repository: resq-software/landing

Length of output: 47


🏁 Script executed:

rg "^\s*time\(" src/utils/logger.ts -A 20

Repository: resq-software/landing

Length of output: 47


🏁 Script executed:

rg "time" src/utils/logger.ts -B 2 -A 10

Repository: resq-software/landing

Length of output: 1538


🏁 Script executed:

wc -l src/utils/logger.ts

Repository: resq-software/landing

Length of output: 88


🏁 Script executed:

sed -n '200,250p' src/utils/logger.ts

Repository: resq-software/landing

Length of output: 1522


🏁 Script executed:

sed -n '210,230p' src/utils/logger.test.ts

Repository: resq-software/landing

Length of output: 481


🏁 Script executed:

sed -n '205,230p' src/utils/logger.test.ts

Repository: resq-software/landing

Length of output: 669


🏁 Script executed:

ast-grep --pattern 'test($_, async () => { $$$ })' src/utils/logger.test.ts

Repository: resq-software/landing

Length of output: 1552


🏁 Script executed:

sed -n '250,280p' src/utils/logger.ts

Repository: resq-software/landing

Length of output: 1007


🏁 Script executed:

sed -n '245,265p' src/utils/logger.ts

Repository: resq-software/landing

Length of output: 756


🏁 Script executed:

rg "async time" src/utils/logger.ts -A 15

Repository: resq-software/landing

Length of output: 682


Await the .rejects assertion.

The logger.time() method returns a Promise. Without await or return, the async assertion chain is not waited on, and the test body completes without validating the rejection. This creates a false positive—the test passes even if the assertion fails.

✅ Minimal fix
-      expect(
+      await expect(
         logger.time("failing", () => {
           throw new Error("boom");
         }),
       ).rejects.toThrow("boom");
📝 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
expect(
logger.time("failing", () => {
throw new Error("boom");
}),
).rejects.toThrow("boom");
await expect(
logger.time("failing", () => {
throw new Error("boom");
}),
).rejects.toThrow("boom");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/logger.test.ts` around lines 219 - 223, The test uses
expect(...).rejects.toThrow on the Promise returned by logger.time("failing",
...) but does not await or return that expectation, so the test can falsely
pass; update the test to await the assertion (or return the expect promise) so
the rejection is actually asserted — locate the failing test that calls
logger.time("failing", () => { throw new Error("boom"); }) and add an await
before expect(...) or return the expect(...) to ensure the promise rejection is
validated.

@gemini-code-assist
Copy link
Copy Markdown

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

Copy link
Copy Markdown

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/sitemap.ts (1)

1-6: ⚠️ Potential issue | 🟡 Minor

Missing Apache-2.0 license header.

The file is missing the required license header at the top.

Proposed fix to add the license header
+/**
+ * Copyright 2026 ResQ Software
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 /**
  * Generates the static sitemap metadata for the marketing routes.
  */

As per coding guidelines: "All source files must include the Apache-2.0 license header".

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

In `@src/app/sitemap.ts` around lines 1 - 6, Add the required Apache-2.0 license
header to the top of src/app/sitemap.ts so the file includes the standard
license block (SPDX identifier and copyright/owner information) before any
imports; update the header consistently with other project files to match
formatting and owner/year, ensuring the licence appears above the existing
comments and the import of MetadataRoute and config.
♻️ Duplicate comments (1)
src/app/[locale]/loading.tsx (1)

43-48: ⚠️ Potential issue | 🔴 Critical

Debug timeout value left in code — 30-second delay will block users.

The setTimeout delay is set to 30_000 ms (30 seconds) with a TODO comment indicating it should be 1200. This appears to be debug/testing code that was inadvertently committed and will cause an unacceptable delay before the loading screen dismisses.

Additionally, the setTimeout return value is not captured and cleared in the effect cleanup, which can cause state updates after unmount.

Suggested fix
   useEffect(() => {
     let currentIndex = 0;
+    let hideTimeout: ReturnType<typeof setTimeout> | undefined;

     document.body.style.overflow = 'hidden';
     document.body.setAttribute('aria-busy', 'true');

     const interval = setInterval(() => {
       if (currentIndex < bootSequence.length) {
         setLogs(prev => [...prev, bootSequence[currentIndex]]);
         setProgress(((currentIndex + 1) / bootSequence.length) * 100);
         currentIndex++;
       } else {
         clearInterval(interval);
-        setTimeout(() => {
+        hideTimeout = setTimeout(() => {
           setLoading(false);
           document.body.style.overflow = '';
           document.body.removeAttribute('aria-busy');
-        }, 30_000); // TODO: revert to 1200 after design review
+        }, 1200);
       }
     }, 450);

     return () => {
       clearInterval(interval);
+      if (hideTimeout) clearTimeout(hideTimeout);
       document.body.style.overflow = '';
       document.body.removeAttribute('aria-busy');
     };
   }, []);

,

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

In `@src/app/`[locale]/loading.tsx around lines 43 - 48, Replace the debug
30_000ms delay with the intended 1200ms and ensure the timer is cleaned up: in
the effect that calls setTimeout (the block that calls setLoading(false),
document.body.style.overflow = '', and
document.body.removeAttribute('aria-busy')), store the setTimeout return value
in a variable (e.g., timerId) and call clearTimeout(timerId) in the
effect/component cleanup to avoid state updates after unmount; keep the same
state update (setLoading) and DOM cleanup calls but ensure the timer is cleared
on unmount.
🧹 Nitpick comments (3)
src/features/marketing/sections/competitive-table.tsx (1)

19-26: Consider adding explicit TypeScript types for better maintainability.

The COMPETITORS array uses mixed boolean | string values in checks, but there's no type definition ensuring each entry has exactly 6 checks matching the 6 features. Adding explicit types would catch mismatches at compile time.

♻️ Suggested type definition
+type CheckValue = boolean | "partial";
+
+interface Competitor {
+  name: string;
+  checks: [CheckValue, CheckValue, CheckValue, CheckValue, CheckValue, CheckValue];
+  highlight?: boolean;
+  note: string;
+}
+
-const COMPETITORS = [
+const COMPETITORS: Competitor[] = [
   { name: "ResQ", checks: [true, true, true, true, true, true], highlight: true, note: "OS-level coordination layer" },

This ensures checks length matches FEATURES length and restricts values to valid types.

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

In `@src/features/marketing/sections/competitive-table.tsx` around lines 19 - 26,
The COMPETITORS constant uses a mixed boolean|string checks array with no type
safety; add an explicit TypeScript type (e.g., a Competitor interface or type
alias) that defines name:string, note?:string, highlight?:boolean and checks as
a fixed-length tuple of exactly six elements with allowed values boolean |
"partial" (or a union type like Check = true | false | "partial") and then
annotate COMPETITORS: Competitor[] (or readonly Competitor[]) to ensure
compile-time validation that each entry has exactly six checks matching
FEATURES; update any usages expecting FEATURES to use the same length/type to
keep them in sync.
src/features/marketing/components/footer.tsx (1)

52-61: Missing error handling for status fetch failure.

The catch(() => {}) silently swallows errors. While the UI gracefully falls back to "unknown" status, consider logging the error for debugging purposes.

🔧 Suggested improvement
       .then((json) => {
         if (json.success && json.data) {
           setStatus(json.data);
         }
       })
-      .catch(() => {});
+      .catch((err) => {
+        console.error("Failed to fetch status:", err);
+      });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/marketing/components/footer.tsx` around lines 52 - 61, The
current useEffect that calls fetch("/api/status") swallows errors in catch(() =>
{})—replace the empty catch with proper error logging so failures are visible;
update the catch to log the caught error (and optionally any non-OK response
details) using console.error (or the app logger) and retain the existing
fallback behavior via setStatus, referencing the useEffect block, the
fetch("/api/status") call, and the setStatus state updater in the Footer
component.
src/features/marketing/components/locale-switcher.tsx (1)

78-109: Consider adding keyboard navigation for the dropdown.

The compact variant dropdown has good ARIA attributes (role="listbox", role="option", aria-selected), but lacks keyboard navigation. Users cannot use arrow keys to navigate options, which is expected behavior for listbox patterns per WCAG.

This is a minor accessibility enhancement that could be deferred.

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

In `@src/features/marketing/components/locale-switcher.tsx` around lines 78 - 109,
The dropdown lacks keyboard navigation for the listbox pattern; add a keydown
handler on the listbox container (the element with role="listbox" in
locale-switcher.tsx) that tracks a focused option index (local state) and
responds to ArrowDown/ArrowUp/Home/End to move the index, and Enter/Space to
activate switchLocale(loc) for the focused item; ensure the handler calls
setOpen(false) when activating, updates aria-selected appropriately (compare
routing.locales[index] to locale), and moves DOM focus to the corresponding
option element (or use aria-activedescendant) so keyboard users can navigate the
LOCALE_LABELS options and select one without a mouse.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/features/marketing/components/footer.tsx`:
- Line 186: The X SVG in the Footer component lacks an aria-labelledby
connection to its <title>, so update the SVG element used in footer.tsx (the X
icon) to include an aria-labelledby attribute pointing to a unique id and give
the <title> element that same id (e.g., "x-title-<unique>"). Ensure the title
text is descriptive (e.g., "X") and keep role="img"; use a unique id to avoid
collisions if the icon is rendered multiple times.
- Line 195: The GitHub SVG in the Footer component lacks an aria-labelledby
association for its <title>, so add a unique id to the <title> element (e.g.,
"github-title") and add aria-labelledby="github-title" on the <svg> element (the
SVG block containing <title>GitHub</title>), ensuring the title is properly
associated for screen readers; keep the existing role="img" and
className="size-5".
- Line 177: The SVG in the footer component has the <title> element placed after
the <path>, causing the Biome noSvgWithoutTitle lint error; move the <title>
element to be the first child of the <svg> with className "size-5" (the LinkedIn
icon SVG) so <title> appears before the <path> inside the SVG element in the
Footer component.

---

Outside diff comments:
In `@src/app/sitemap.ts`:
- Around line 1-6: Add the required Apache-2.0 license header to the top of
src/app/sitemap.ts so the file includes the standard license block (SPDX
identifier and copyright/owner information) before any imports; update the
header consistently with other project files to match formatting and owner/year,
ensuring the licence appears above the existing comments and the import of
MetadataRoute and config.

---

Duplicate comments:
In `@src/app/`[locale]/loading.tsx:
- Around line 43-48: Replace the debug 30_000ms delay with the intended 1200ms
and ensure the timer is cleaned up: in the effect that calls setTimeout (the
block that calls setLoading(false), document.body.style.overflow = '', and
document.body.removeAttribute('aria-busy')), store the setTimeout return value
in a variable (e.g., timerId) and call clearTimeout(timerId) in the
effect/component cleanup to avoid state updates after unmount; keep the same
state update (setLoading) and DOM cleanup calls but ensure the timer is cleared
on unmount.

---

Nitpick comments:
In `@src/features/marketing/components/footer.tsx`:
- Around line 52-61: The current useEffect that calls fetch("/api/status")
swallows errors in catch(() => {})—replace the empty catch with proper error
logging so failures are visible; update the catch to log the caught error (and
optionally any non-OK response details) using console.error (or the app logger)
and retain the existing fallback behavior via setStatus, referencing the
useEffect block, the fetch("/api/status") call, and the setStatus state updater
in the Footer component.

In `@src/features/marketing/components/locale-switcher.tsx`:
- Around line 78-109: The dropdown lacks keyboard navigation for the listbox
pattern; add a keydown handler on the listbox container (the element with
role="listbox" in locale-switcher.tsx) that tracks a focused option index (local
state) and responds to ArrowDown/ArrowUp/Home/End to move the index, and
Enter/Space to activate switchLocale(loc) for the focused item; ensure the
handler calls setOpen(false) when activating, updates aria-selected
appropriately (compare routing.locales[index] to locale), and moves DOM focus to
the corresponding option element (or use aria-activedescendant) so keyboard
users can navigate the LOCALE_LABELS options and select one without a mouse.

In `@src/features/marketing/sections/competitive-table.tsx`:
- Around line 19-26: The COMPETITORS constant uses a mixed boolean|string checks
array with no type safety; add an explicit TypeScript type (e.g., a Competitor
interface or type alias) that defines name:string, note?:string,
highlight?:boolean and checks as a fixed-length tuple of exactly six elements
with allowed values boolean | "partial" (or a union type like Check = true |
false | "partial") and then annotate COMPETITORS: Competitor[] (or readonly
Competitor[]) to ensure compile-time validation that each entry has exactly six
checks matching FEATURES; update any usages expecting FEATURES to use the same
length/type to keep them in sync.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 96044511-0d01-45c4-9773-a9e12a113833

📥 Commits

Reviewing files that changed from the base of the PR and between a5721b9 and 10102ce.

📒 Files selected for processing (20)
  • messages/ar.json
  • messages/en.json
  • messages/es.json
  • messages/hi.json
  • messages/zh.json
  • src/app/[locale]/(marketing)/legal/acceptable-use/page.tsx
  • src/app/[locale]/(marketing)/legal/cookie-policy/page.tsx
  • src/app/[locale]/(marketing)/legal/data-processing/page.tsx
  • src/app/[locale]/(marketing)/legal/page.tsx
  • src/app/[locale]/(marketing)/legal/security/page.tsx
  • src/app/[locale]/loading.tsx
  • src/app/sitemap.ts
  • src/features/marketing/components/footer.tsx
  • src/features/marketing/components/locale-switcher.tsx
  • src/features/marketing/components/navbar.tsx
  • src/features/marketing/sections/architecture-diagram.tsx
  • src/features/marketing/sections/competitive-table.tsx
  • src/features/marketing/sections/technology.tsx
  • src/features/marketing/sections/trust-security.tsx
  • src/features/marketing/sections/why-now.tsx
✅ Files skipped from review due to trivial changes (6)
  • messages/hi.json
  • messages/es.json
  • messages/zh.json
  • src/features/marketing/sections/trust-security.tsx
  • messages/en.json
  • messages/ar.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/features/marketing/sections/architecture-diagram.tsx
  • src/features/marketing/sections/technology.tsx
  • src/features/marketing/sections/why-now.tsx

WomB0ComB0 and others added 2 commits March 24, 2026 15:22
…n issues

- Guard Logtail init for missing BETTERSTACK_API_KEY (fixes CI build)
- Update Biome schema 2.4.7→2.4.8 and fix useLiteralKeys lint errors
- Add aria-labelledby to footer SVGs (fixes noSvgWithoutTitle errors)
- Pin CI test job actions to SHA hashes for supply chain safety
- Remove PII logging from contact form action (GDPR/CCPA)
- Clear setTimeout in loading.tsx useEffect cleanup (memory leak)
- Add Apache-2.0 license header to loading.tsx
- Use locale-aware Link and date formatting in blog pages
- Generate locale-prefixed sitemap URLs for all 5 locales
- Fix locale-blind MDX links via relative paths
- Validate frontmatter in content loader instead of unsafe casts
- Remove dead ScrollIntoCenterView helper and ad-hoc test.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@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: 3

♻️ Duplicate comments (1)
src/app/[locale]/(marketing)/blog/[slug]/page.tsx (1)

18-23: ⚠️ Potential issue | 🟠 Major

Localize metadata fallback and back-link label.

Line 23 ("Post Not Found") and Line 58 ("Back to blog") are still hard-coded English, so non-English locales render mixed-language metadata/chrome. Use the active locale dictionary for both strings.

Also applies to: 53-59

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

In `@src/app/`[locale]/(marketing)/blog/[slug]/page.tsx around lines 18 - 23, The
metadata title and the page back-link are hard-coded in English; update
generateMetadata and the page render to use the active locale dictionary
instead: extract the locale from the route params, call the existing
getDictionary (or equivalent) for that locale, and replace the hard-coded "Post
Not Found" passed into constructMetadata and the hard-coded "Back to blog" link
label (lines ~53-59) with the appropriate localized keys (e.g.,
dict.postNotFound and dict.backToBlog) so both the metadata title and back-link
label are rendered in the current locale.
🧹 Nitpick comments (3)
src/features/marketing/components/footer.tsx (2)

52-61: Empty catch block silently swallows fetch errors.

The error is discarded without logging, making debugging harder if the status endpoint fails. Consider at least logging to console in development.

🔧 Suggested improvement
   useEffect(() => {
     fetch("/api/status")
       .then((res) => res.json())
       .then((json) => {
         if (json.success && json.data) {
           setStatus(json.data);
         }
       })
-      .catch(() => {});
+      .catch((error) => {
+        if (process.env.NODE_ENV === "development") {
+          console.error("Failed to fetch status:", error);
+        }
+      });
   }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/marketing/components/footer.tsx` around lines 52 - 61, The empty
catch in the useEffect that calls fetch("/api/status") silently swallows errors;
update the catch on the fetch chain in the component (the useEffect that calls
fetch("/api/status") and then setStatus) to at minimum log the error (e.g.,
console.error) and include a descriptive message, and preferably gate verbose
logging behind a dev check (process.env.NODE_ENV !== 'production') or forward
the error to an existing logger if available.

139-155: Newsletter form is non-functional.

The form prevents default submission but doesn't actually do anything. Consider adding a TODO comment to track this or implementing basic user feedback (e.g., a toast indicating the feature is coming soon).

🔧 Suggested placeholder feedback
           <form
             className="flex w-full max-w-md"
-            onSubmit={(e) => e.preventDefault()}
+            onSubmit={(e) => {
+              e.preventDefault();
+              // TODO: Implement newsletter subscription
+            }}
           >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/marketing/components/footer.tsx` around lines 139 - 155, The
newsletter form currently stops submission in the inline onSubmit handler but
does nothing else; add a minimal placeholder implementation by extracting the
submit logic into a handler (e.g., handleNewsletterSubmit) and either (a) add a
TODO comment referencing handleNewsletterSubmit to track future work, or (b)
implement immediate user feedback in that handler (e.g., trigger a toast/alert
or set local state like isSubscribed/message to show a "coming soon" or
"subscription received" message), and wire that handler to the form's onSubmit
(and ensure the submit button remains type="submit"); update the input to be
controlled or read its value in handleNewsletterSubmit if you plan to show it in
the message.
src/lib/better-stack.ts (1)

27-33: Consider extracting a LoggerLike type instead of force-casting to Logtail

Line 32 force-casts a partial stub to Logtail. While the stub correctly implements all methods used within this file (debug, info, warn, error, flush) and the logger is wrapped by typed helper functions that provide type safety at the API boundary, extracting an explicit interface would improve maintainability and prevent future accidental unsafe casts.

Suggested refactor
 import { env } from '@/env';
 import { Logtail } from '@logtail/node';

+type LoggerLike = {
+  debug: (...args: any[]) => Promise<unknown>;
+  info: (...args: any[]) => Promise<unknown>;
+  warn: (...args: any[]) => Promise<unknown>;
+  error: (...args: any[]) => Promise<unknown>;
+  flush: () => Promise<void>;
+};
+
-function createLogger(): Logtail {
+function createLogger(): LoggerLike {
   const token = env.BETTERSTACK_API_KEY;
   if (!token) {
-    const noop = (..._args: unknown[]) => Promise.resolve({} as never);
-    return { debug: noop, info: noop, warn: noop, error: noop, flush: () => Promise.resolve() } as unknown as Logtail;
+    const noop = async (..._args: any[]) => undefined;
+    return {
+      debug: noop,
+      info: noop,
+      warn: noop,
+      error: noop,
+      flush: async () => undefined,
+    };
   }
   return new Logtail(token, {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/better-stack.ts` around lines 27 - 33, The stub in createLogger is
being force-cast to Logtail; instead extract a LoggerLike interface describing
the used methods (debug, info, warn, error, flush), type the noop stub as
LoggerLike, and change createLogger's return type from Logtail to LoggerLike (or
union Logtail|LoggerLike) so callers use the explicit lightweight logger
interface; update any call sites that assume Logtail accordingly (refer to
createLogger, noop, and Logtail).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`[locale]/(marketing)/blog/[slug]/page.tsx:
- Around line 32-37: The formatDate function currently uses new Date(dateStr)
which treats a date-only string as UTC midnight and then toLocaleDateString can
shift it to the previous day in negative-offset timezones; update formatDate to
parse the YYYY-MM-DD string into numeric year, month, and day (e.g., split on
"-" and convert to numbers), construct a Date using Date.UTC(year, month-1, day)
or new Date(Date.UTC(...)), and call toLocaleDateString with an explicit
timeZone: "UTC" option so the displayed date does not drift across timezones.

In `@src/app/`[locale]/loading.tsx:
- Around line 47-51: The timeout for hiding the loading state in the hideTimeout
callback is set to 30_000ms (30s) but the TODO says it should be 1_200ms; change
the delay passed to setTimeout in the hideTimeout setup to 1_200 (or wrap the
30_000 value behind a development-only guard like process.env.NODE_ENV !==
'production' so production uses 1_200ms) and ensure the rest of the callback
still calls setLoading(false), resets document.body.style.overflow, and removes
the aria-busy attribute as implemented in this component (the
hideTimeout/setLoading/document.body.* logic).
- Around line 37-38: The loading component in loading.tsx and the navbar in
navbar.tsx both directly set document.body.style.overflow, causing a race when
the loading timeout clears overflow while the mobile menu is open; replace
direct DOM writes with a shared scroll-lock manager that provides
reference-counted lock/unlock functions (e.g.,
acquireScrollLock/releaseScrollLock or lock/unlock) exported from a new module
and call those from the Loading component (on mount/timeout/cleanup) and from
the Navbar (when opening/closing the mobile menu) so each consumer
increments/decrements a counter and only clears overflow when the count reaches
zero; update Loading and Navbar to use these manager functions instead of
directly setting document.body.style.overflow or aria attributes.

---

Duplicate comments:
In `@src/app/`[locale]/(marketing)/blog/[slug]/page.tsx:
- Around line 18-23: The metadata title and the page back-link are hard-coded in
English; update generateMetadata and the page render to use the active locale
dictionary instead: extract the locale from the route params, call the existing
getDictionary (or equivalent) for that locale, and replace the hard-coded "Post
Not Found" passed into constructMetadata and the hard-coded "Back to blog" link
label (lines ~53-59) with the appropriate localized keys (e.g.,
dict.postNotFound and dict.backToBlog) so both the metadata title and back-link
label are rendered in the current locale.

---

Nitpick comments:
In `@src/features/marketing/components/footer.tsx`:
- Around line 52-61: The empty catch in the useEffect that calls
fetch("/api/status") silently swallows errors; update the catch on the fetch
chain in the component (the useEffect that calls fetch("/api/status") and then
setStatus) to at minimum log the error (e.g., console.error) and include a
descriptive message, and preferably gate verbose logging behind a dev check
(process.env.NODE_ENV !== 'production') or forward the error to an existing
logger if available.
- Around line 139-155: The newsletter form currently stops submission in the
inline onSubmit handler but does nothing else; add a minimal placeholder
implementation by extracting the submit logic into a handler (e.g.,
handleNewsletterSubmit) and either (a) add a TODO comment referencing
handleNewsletterSubmit to track future work, or (b) implement immediate user
feedback in that handler (e.g., trigger a toast/alert or set local state like
isSubscribed/message to show a "coming soon" or "subscription received"
message), and wire that handler to the form's onSubmit (and ensure the submit
button remains type="submit"); update the input to be controlled or read its
value in handleNewsletterSubmit if you plan to show it in the message.

In `@src/lib/better-stack.ts`:
- Around line 27-33: The stub in createLogger is being force-cast to Logtail;
instead extract a LoggerLike interface describing the used methods (debug, info,
warn, error, flush), type the noop stub as LoggerLike, and change createLogger's
return type from Logtail to LoggerLike (or union Logtail|LoggerLike) so callers
use the explicit lightweight logger interface; update any call sites that assume
Logtail accordingly (refer to createLogger, noop, and Logtail).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: def6ebe4-2e23-4137-87bc-faee85c8ebaf

📥 Commits

Reviewing files that changed from the base of the PR and between 10102ce and 732f7c3.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • .github/workflows/ci.yml
  • biome.json
  • content/posts/building-for-zero-connectivity.mdx
  • content/posts/introducing-resq-tactical-os.mdx
  • package.json
  • src/actions/contact/submit.ts
  • src/app/[locale]/(marketing)/blog/[slug]/page.tsx
  • src/app/[locale]/(marketing)/blog/page.tsx
  • src/app/[locale]/loading.tsx
  • src/app/api/_elysia/shared/auth.ts
  • src/app/sitemap.ts
  • src/features/marketing/components/footer.tsx
  • src/lib/better-stack.ts
  • src/lib/content.ts
  • src/utils/helpers.ts
  • src/utils/logger.ts
✅ Files skipped from review due to trivial changes (4)
  • src/app/api/_elysia/shared/auth.ts
  • biome.json
  • src/utils/logger.ts
  • content/posts/introducing-resq-tactical-os.mdx
🚧 Files skipped from review as they are similar to previous changes (5)
  • package.json
  • src/app/[locale]/(marketing)/blog/page.tsx
  • src/app/sitemap.ts
  • src/actions/contact/submit.ts
  • src/lib/content.ts

Comment on lines +32 to +37
function formatDate(dateStr: string, locale: string) {
return new Date(dateStr).toLocaleDateString(locale, {
year: "numeric",
month: "long",
day: "numeric",
});
Copy link
Copy Markdown

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

🌐 Web query:

In JavaScript/TypeScript, does new Date("YYYY-MM-DD")parse as UTC, and cantoLocaleDateString show the previous calendar day in negative UTC offsets (e.g., US time zones)? Please cite MDN/ECMAScript references.

💡 Result:

Yes, new Date("YYYY-MM-DD") parses the string as a date-only form in the Date Time String Format, which is interpreted as UTC time (midnight UTC on that date), per ECMAScript specification and MDN documentation. This is due to a historical spec decision for web compatibility, even though it differs from strict ISO 8601. Yes, toLocaleDateString can show the previous calendar day in negative UTC offsets (e.g., US time zones like EST/UTC-5). The Date object represents YYYY-MM-DD 00:00:00 UTC. When formatted in a local time zone behind UTC (negative offset), this UTC midnight converts to the previous day's evening (e.g., 2019-01-01T00:00:00Z becomes 2018-12-31 19:00:00 EST), so the date part is the prior day.

Citations:


🏁 Script executed:

cat -n src/app/[locale]/\(marketing\)/blog/\[slug\]/page.tsx | sed -n '25,45p'

Repository: resq-software/landing

Length of output: 690


🏁 Script executed:

cat -n src/app/[locale]/\(marketing\)/blog/\[slug\]/page.tsx | head -35

Repository: resq-software/landing

Length of output: 1324


🏁 Script executed:

cat -n src/app/[locale]/\(marketing\)/blog/\[slug\]/page.tsx | tail -30

Repository: resq-software/landing

Length of output: 1437


🏁 Script executed:

rg -n "getPostBySlug\|post\.date" src/lib/content --type ts --type tsx -A 3 -B 1

Repository: resq-software/landing

Length of output: 92


🏁 Script executed:

rg -n "getPostBySlug\|post\.date" src/lib/content -A 3 -B 1

Repository: resq-software/landing

Length of output: 120


🏁 Script executed:

fd content -t d src/

Repository: resq-software/landing

Length of output: 47


🏁 Script executed:

rg -n "getPostBySlug" src/ -A 5

Repository: resq-software/landing

Length of output: 1887


🏁 Script executed:

cat -n src/lib/content.ts | head -70

Repository: resq-software/landing

Length of output: 2594


🏁 Script executed:

fd -e mdx content/posts | head -3 | xargs -I {} sh -c 'echo "=== {} ===" && head -20 "{}"'

Repository: resq-software/landing

Length of output: 422


🏁 Script executed:

fd . content/posts -e mdx | head -3 | xargs -I {} sh -c 'echo "=== {} ===" && head -20 "{}"'

Repository: resq-software/landing

Length of output: 3235


Avoid timezone drift when formatting YYYY-MM-DD post dates.

Line 33 parses a date-only string with new Date(dateStr), which is interpreted as UTC midnight. In browsers with negative UTC offsets (common in the US), toLocaleDateString converts this UTC time to the local date, displaying the previous day. Parse as UTC date parts and format with timeZone: "UTC" to preserve the intended date regardless of the user's timezone.

🛠️ Proposed fix
 function formatDate(dateStr: string, locale: string) {
-  return new Date(dateStr).toLocaleDateString(locale, {
-    year: "numeric",
-    month: "long",
-    day: "numeric",
-  });
+  const [year, month, day] = dateStr.split("-").map(Number);
+  const date = new Date(Date.UTC(year, month - 1, day));
+  return new Intl.DateTimeFormat(locale, {
+    year: "numeric",
+    month: "long",
+    day: "numeric",
+    timeZone: "UTC",
+  }).format(date);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[locale]/(marketing)/blog/[slug]/page.tsx around lines 32 - 37, The
formatDate function currently uses new Date(dateStr) which treats a date-only
string as UTC midnight and then toLocaleDateString can shift it to the previous
day in negative-offset timezones; update formatDate to parse the YYYY-MM-DD
string into numeric year, month, and day (e.g., split on "-" and convert to
numbers), construct a Date using Date.UTC(year, month-1, day) or new
Date(Date.UTC(...)), and call toLocaleDateString with an explicit timeZone:
"UTC" option so the displayed date does not drift across timezones.

Comment on lines +37 to +38
document.body.style.overflow = 'hidden';
document.body.setAttribute('aria-busy', 'true');
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find all places in the codebase that manipulate document.body.style.overflow
# to assess the scope of potential conflicts.

rg -n "document\.body\.style\.overflow" --type=ts --type=tsx -C2

Repository: resq-software/landing

Length of output: 92


🏁 Script executed:

#!/bin/bash
# Fix: Use -t ts flag (TypeScript covers both .ts and .tsx) or search without type filter
rg -n "document\.body\.style\.overflow" -t ts -C 2

Repository: resq-software/landing

Length of output: 1668


🏁 Script executed:

#!/bin/bash
# Examine the loading.tsx file to understand the context and component structure
cat -n src/app/[locale]/loading.tsx

Repository: resq-software/landing

Length of output: 5601


🏁 Script executed:

#!/bin/bash
# Examine the navbar.tsx file to understand the mobile menu management
cat -n src/features/marketing/components/navbar.tsx

Repository: resq-software/landing

Length of output: 14705


Race condition with navbar and loading component both managing body scroll lock.

Both loading.tsx (lines 37, 49, 58) and navbar.tsx (lines 188, 190) independently manipulate document.body.style.overflow without coordination. Since the loading component runs for 30 seconds on mount, the mobile menu can be opened during this window. If the loading timeout fires while the menu is open, the component clears the overflow constraint, allowing the body to scroll behind the open menu.

Consider implementing a shared scroll lock manager or ref-counting approach to prevent conflicts between these components.

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

In `@src/app/`[locale]/loading.tsx around lines 37 - 38, The loading component in
loading.tsx and the navbar in navbar.tsx both directly set
document.body.style.overflow, causing a race when the loading timeout clears
overflow while the mobile menu is open; replace direct DOM writes with a shared
scroll-lock manager that provides reference-counted lock/unlock functions (e.g.,
acquireScrollLock/releaseScrollLock or lock/unlock) exported from a new module
and call those from the Loading component (on mount/timeout/cleanup) and from
the Navbar (when opening/closing the mobile menu) so each consumer
increments/decrements a counter and only clears overflow when the count reaches
zero; update Loading and Navbar to use these manager functions instead of
directly setting document.body.style.overflow or aria attributes.

Comment on lines +47 to +51
hideTimeout = setTimeout(() => {
setLoading(false);
document.body.style.overflow = '';
document.body.removeAttribute('aria-busy');
}, 30_000); // TODO: revert to 1200 after design review
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Resolve the TODO before merging: 30-second delay is likely unintended for production.

The current 30,000ms (30 seconds) timeout will cause users to wait half a minute on the loading screen after the boot sequence completes. The TODO indicates this should be 1200ms.

If this is intentionally left for design review, consider gating it behind a development-only check to avoid accidentally shipping this to production.

Suggested fix
         hideTimeout = setTimeout(() => {
           setLoading(false);
           document.body.style.overflow = '';
           document.body.removeAttribute('aria-busy');
-        }, 30_000); // TODO: revert to 1200 after design review
+        }, 1200);
📝 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
hideTimeout = setTimeout(() => {
setLoading(false);
document.body.style.overflow = '';
document.body.removeAttribute('aria-busy');
}, 30_000); // TODO: revert to 1200 after design review
hideTimeout = setTimeout(() => {
setLoading(false);
document.body.style.overflow = '';
document.body.removeAttribute('aria-busy');
}, 1200);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[locale]/loading.tsx around lines 47 - 51, The timeout for hiding
the loading state in the hideTimeout callback is set to 30_000ms (30s) but the
TODO says it should be 1_200ms; change the delay passed to setTimeout in the
hideTimeout setup to 1_200 (or wrap the 30_000 value behind a development-only
guard like process.env.NODE_ENV !== 'production' so production uses 1_200ms) and
ensure the rest of the callback still calls setLoading(false), resets
document.body.style.overflow, and removes the aria-busy attribute as implemented
in this component (the hideTimeout/setLoading/document.body.* logic).

Incorporated as a Delaware C Corp — update all copyright headers,
legal pages, and config to reflect the official entity name.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions bot added size/XL C-Documentation Improvements or additions to documentation size/XXL and removed size/XXL labels Mar 25, 2026
Copy link
Copy Markdown

@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

♻️ Duplicate comments (2)
src/app/[locale]/loading.tsx (2)

47-52: ⚠️ Potential issue | 🟠 Major

Reduce the post-boot delay for production.

Line 51 keeps users on the loading screen for 30 seconds, which is likely a production UX bug (the TODO already indicates 1200ms).

Suggested change
-        }, 30_000); // TODO: revert to 1200 after design review
+        }, process.env.NODE_ENV === 'production' ? 1_200 : 30_000);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[locale]/loading.tsx around lines 47 - 52, The timeout that clears
the post-boot loading state uses 30_000ms causing an excessive 30s delay; change
the setTimeout delay in the hideTimeout assignment from 30_000 to 1_200 (or
1200) so setLoading(false), document.body.style.overflow reset, and
document.body.removeAttribute('aria-busy') run after ~1.2s instead of 30s (look
for the hideTimeout = setTimeout(...) block).

37-39: ⚠️ Potential issue | 🟡 Minor

Avoid uncoordinated global scroll-lock writes.

This component directly mutates document.body.style.overflow, and src/features/marketing/components/navbar.tsx (Lines 177-192) does the same. A release in either component can unlock scrolling while the other still needs it.

Use a shared lock manager (ref-counted acquire/release) instead of direct writes in both places.

Also applies to: 49-50, 58-59

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

In `@src/app/`[locale]/loading.tsx around lines 37 - 39, Replace direct mutations
of document.body.style.overflow in the Loading component and navbar by using a
shared ref-counted lock manager: create a small module exporting
acquireScrollLock() and releaseScrollLock() (names must match imports in
src/app/[locale]/loading.tsx and src/features/marketing/components/navbar.tsx),
implement it so the first acquire sets document.body.style.overflow = 'hidden'
and subsequent acquires increment a counter, and release decrements the counter
and only restores overflow when count reaches zero; update loading.tsx to call
acquireScrollLock() when mounted and releaseScrollLock() on unmount (replace the
direct document.body writes), and do the same in navbar.tsx so both components
coordinate the scroll lock.
🧹 Nitpick comments (4)
src/app/[locale]/(marketing)/contact/page.tsx (2)

116-129: Consider using app constants instead of hardcoded company info.

The company info bar duplicates values already defined in app.company (legal, stage, locations). Using the constants would ensure consistency and single-source-of-truth.

♻️ Suggested change
       <section aria-label="Company information" className="border-t border-border-default bg-bg-secondary py-8">
         <div className="mx-auto flex max-w-6xl flex-wrap items-center justify-center gap-x-6 gap-y-2 px-6">
           <span className="font-mono text-xs uppercase tracking-widest text-text-muted">
-            ResQ Systems, Inc.
+            {app.company.legal}
           </span>
           <span className="text-text-muted" aria-hidden="true">|</span>
           <span className="font-mono text-xs uppercase tracking-widest text-text-muted">
-            Seed Stage &middot; Q1 2026
+            {app.company.stage} &middot; Q1 2026
           </span>
           <span className="text-text-muted" aria-hidden="true">|</span>
           <span className="font-mono text-xs uppercase tracking-widest text-text-muted">
-            New York + Texas
+            {app.company.locations.join(" + ")}
           </span>
         </div>
       </section>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[locale]/(marketing)/contact/page.tsx around lines 116 - 129, The
company info section currently hardcodes company details; replace those literals
with the centralized constants from app.company (use app.company.legal,
app.company.stage, app.company.locations) so the bar reads the single source of
truth; import or reference the existing app.company object where page.tsx
renders the <section> and substitute the three spans (legal, stage, locations)
to use those properties while preserving the separators and accessibility
attributes.

11-11: Using next/link for external URLs is unconventional.

The contactLinks array only contains external URLs (docs, GitHub, NuGet, PyPI). While next/link can handle external URLs, using a plain <a> tag is more idiomatic for external navigation since Next.js Link is optimized for client-side routing within the app.

♻️ Suggested change
-import Link from "next/link";
+// No Link import needed - using <a> for external links

...

-                <Link
+                <a
                   key={link.label}
                   href={link.href}
                   target="_blank"
                   rel="noopener noreferrer"
                   className="group flex flex-col gap-3 border border-border-default bg-bg-card p-4 transition-opacity hover:opacity-80"
                 >
                   ...
-                </Link>
+                </a>

Also applies to: 85-103

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

In `@src/app/`[locale]/(marketing)/contact/page.tsx at line 11, The file imports
Link from "next/link" and uses it to render external URLs from the contactLinks
array; replace those Link usages with plain anchor elements (<a>) using
target="_blank" and rel="noopener noreferrer" for safety, and remove the unused
Link import. Locate the contactLinks array and each JSX rendering that
references Link (around the block where Link is imported and the map rendering
lines ~85-103) and change to <a href={url} ...>text</a> while preserving styling
and accessibility attributes.
src/features/marketing/sections/footer-cta.tsx (1)

66-70: Consider using CSS variables instead of hardcoded colors.

The input border (#4A5272) and button background (#C13130) use hardcoded hex values while other elements use CSS variables (bg-bg-card, text-text-primary, etc.). For theme consistency, consider using the existing design tokens.

♻️ Suggested change
-            className="min-w-0 flex-1 border border-[`#4A5272`] bg-bg-card px-4 py-4 font-mono text-sm uppercase tracking-wider text-text-primary placeholder:text-text-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-blue"
+            className="min-w-0 flex-1 border border-border-default bg-bg-card px-4 py-4 font-mono text-sm uppercase tracking-wider text-text-primary placeholder:text-text-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-blue"
           />
           <button
             type="submit"
-            className="bg-[`#C13130`] px-8 py-4 font-mono text-sm font-semibold uppercase tracking-widest text-white motion-safe:transition-colors motion-safe:duration-200 hover:brightness-110"
+            className="bg-accent-red px-8 py-4 font-mono text-sm font-semibold uppercase tracking-widest text-white motion-safe:transition-colors motion-safe:duration-200 hover:brightness-110"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/marketing/sections/footer-cta.tsx` around lines 66 - 70, The
input and submit button are using hardcoded hex colors (border `#4A5272` and
button bg `#C13130`); update the className on the input element and the button
element in footer-cta.tsx to use the existing CSS design tokens instead (e.g.,
replace border-[`#4A5272`] with the project border color variable like
border-border-muted or similar, and replace bg-[`#C13130`] with the primary/accent
button background token such as bg-accent-red or bg-primary) so the component
matches the other utility classes (bg-bg-card, text-text-primary) and supports
theming consistently.
src/proxy.ts (1)

107-107: Avoid splitting the skip rules between matcher and shouldBypassProxy().

Line 107 now hard-excludes cases that shouldBypassProxy() already filters, while other skips still live only in the runtime helper. That overlap makes some branches unreachable and makes future routing changes easy to miss.

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

In `@src/proxy.ts` at line 107, The matcher currently hard-excludes routes with
the negative lookahead while shouldBypassProxy() also filters those same cases,
causing overlapping/unreachable branches; consolidate skip rules into one place
by removing the negative lookahead from matcher (change matcher to a broader
pattern, e.g. String.raw`/(.*)/` or the intended non-exclusion pattern) and
ensure shouldBypassProxy() contains the full canonical list of skip conditions
(api, trpc, _next, _vercel, files with extensions, etc.), or alternatively move
all skip logic into matcher and simplify shouldBypassProxy() to only
runtime-only checks — update the matcher and the shouldBypassProxy() function so
all skip rules live in the same single source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`[locale]/(marketing)/blog/page.tsx:
- Around line 17-23: The formatDate function suffers from timezone drift because
new Date(dateStr) treats YYYY-MM-DD as UTC midnight and negative offsets can
render the previous day; update formatDate to parse the dateStr (e.g.,
split/regex for year, month, day) and construct the Date using Date.UTC with a
safe midday time (e.g., new Date(Date.UTC(year, month-1, day, 12, 0, 0))) before
calling toLocaleDateString(locale, ...); this change in the formatDate function
prevents local timezone offsets from shifting the displayed calendar day.

In `@src/features/marketing/components/navbar.tsx`:
- Around line 203-205: The nav element's aria-label is hardcoded ("Main
navigation"); add a key (e.g., "mainNavigation") to the Nav i18n catalog with
translated values and use the same translation lookup used for other menu
strings in this component (the Nav/navbar component) to set aria-label
dynamically; update the nav JSX to call the translation function (the same
helper used for other labels in this file) and pass the localized string into
aria-label so screen readers receive the translated landmark label.
- Around line 24-50: NavListItem currently always renders a raw <a>, causing
internal links (e.g., items returned by usePlatformItems like "/platform" and
"/protocol") to do full-page navigations; update NavListItem to mirror the
mobile menu logic by importing Link from "next/link" and conditionally rendering
<Link href={href}>...children...</Link> for non-external/internal hrefs and
falling back to a plain <a href={href} ...> for external URLs, preserving
existing props (target, rel, className) and accessibility attributes; ensure any
external detection helper used in the mobile menu (or equivalent logic) is
reused so internal routes use client-side navigation.
- Around line 303-361: Replace the current manual mobile menu DOM (the div with
id="mobile-menu" that uses inert={!menuOpen}, menuOpen state, and setMenuOpen)
with a Radix Dialog primitive: wrap the menu in Dialog.Root open={menuOpen}
onOpenChange={setMenuOpen}, render the backdrop via Dialog.Overlay and the menu
contents inside Dialog.Content (or Dialog.Portal > Dialog.Content) so Radix
handles focus trapping and Escape/close behavior; remove the inert prop and
existing manual focus logic/escape handlers. Keep the existing mapping over
mobileLinks (including external link handling, target/rel, icon and sr-only
text), the mobile CTA Link (href="/contact") and LocaleSwitcher inside the
Dialog.Content, and import/use Dialog, DialogOverlay, DialogContent, DialogClose
from `@radix-ui/react-dialog` as needed. Ensure styling/classes from the original
container are applied to Dialog.Content and that setMenuOpen(false) is preserved
for link onClick to close the dialog.

In `@src/features/marketing/sections/architecture-diagram.tsx`:
- Line 95: The table element in the ArchitectureDiagram component uses a
non-existent Tailwind class "min-w-125" (seen in the table's className "w-full
min-w-125"), which produces no CSS; either replace that token with an arbitrary
Tailwind value like "min-w-[500px]" in the table's className (and likewise in
technology.tsx where used) or add "min-w-125" to your Tailwind theme scale
inside the `@theme` inline block in src/styles/globals.css so the class is
generated.

In `@src/features/marketing/sections/footer-cta.tsx`:
- Around line 47-50: The footer CTA currently uses a mailto form (<form
action="mailto:...">) but displays a misleading "ENCRYPTED TRANSMISSION //
256-BIT SSL" claim; either remove/reword that security text or replace the
mailto form with the existing ContactForm component that calls the submitContact
server action to perform secure submission. Locate the <form
action="mailto:contact@resq.software" ...> in footer-cta.tsx (and the duplicate
instance) and either: 1) delete or change the security copy to a non-technical,
accurate phrase (e.g., "Contact via your email client") or 2) swap the form
markup for the ContactForm component and wire it to submitContact so submissions
go through the server action rather than mailto.

In `@src/features/marketing/sections/hero.tsx`:
- Around line 13-17: SCENARIOS[videoIndex] can be undefined causing HeroHud to
read properties off undefined; clamp or defensively resolve the scenario before
lookup: compute an index = Math.max(0, Math.min(videoIndex, SCENARIOS.length -
1)) or select a fallback scenario when SCENARIOS[videoIndex] is undefined, then
pass that safe scenario into HeroHud and any other places referencing
SCENARIOS[videoIndex] (e.g., where videoIndex is used to read uptimeBase and
location), and apply the same defensive lookup for the other occurrences noted
(the other SCENARIOS access points).

In `@src/lib/content.ts`:
- Around line 59-62: getPostBySlug currently uses the incoming slug directly in
path.join which allows path traversal; validate or sanitize the slug before
building filePath (e.g., enforce an allowlist regex like only
letters/numbers/hyphens/underscores or compute resolved =
path.resolve(POSTS_DIR, `${slug}.mdx`) and ensure
resolved.startsWith(path.resolve(POSTS_DIR))). If validation fails, return null
(or throw) and only then read the file; update references to getPostBySlug,
POSTS_DIR, and filePath accordingly.

In `@src/proxy.ts`:
- Around line 92-99: The code sets the nonce and CSP only on the response, but
Next.js requires the CSP header (with nonce) on the forwarded request so SSR can
inject nonce attributes; update the flow around intlMiddleware and
applySecurityHeaders to add the nonce and CSP into the outgoing request headers
(e.g., build or mutate requestHeaders and call NextResponse.next({ request: {
headers: requestHeaders } })) rather than only setting them on the response, or
ensure applySecurityHeaders writes both request and response headers; modify
where nonce is generated (keep using crypto.randomUUID().replaceAll("-", ""))
and ensure the same nonce is applied to the CSP header and forwarded on the
request before intlMiddleware/NextResponse.next is invoked.

In `@src/utils/helpers.ts`:
- Around line 75-78: The code reads VITE_BASE_URL / NEXT_PUBLIC_BASE_URL /
BASE_URL directly from process.env (and uses optional chaining on process) which
violates the env validation rule; add VITE_BASE_URL, NEXT_PUBLIC_BASE_URL, and
BASE_URL to the clientEnvSchema in src/env.ts (so they are validated and exposed
on the client as needed), export the validated env, then replace the direct
process?.env access in the helper with the imported validated env (e.g., use
env.VITE_BASE_URL || env.NEXT_PUBLIC_BASE_URL || env.BASE_URL) and remove the
optional chaining on process.

---

Duplicate comments:
In `@src/app/`[locale]/loading.tsx:
- Around line 47-52: The timeout that clears the post-boot loading state uses
30_000ms causing an excessive 30s delay; change the setTimeout delay in the
hideTimeout assignment from 30_000 to 1_200 (or 1200) so setLoading(false),
document.body.style.overflow reset, and
document.body.removeAttribute('aria-busy') run after ~1.2s instead of 30s (look
for the hideTimeout = setTimeout(...) block).
- Around line 37-39: Replace direct mutations of document.body.style.overflow in
the Loading component and navbar by using a shared ref-counted lock manager:
create a small module exporting acquireScrollLock() and releaseScrollLock()
(names must match imports in src/app/[locale]/loading.tsx and
src/features/marketing/components/navbar.tsx), implement it so the first acquire
sets document.body.style.overflow = 'hidden' and subsequent acquires increment a
counter, and release decrements the counter and only restores overflow when
count reaches zero; update loading.tsx to call acquireScrollLock() when mounted
and releaseScrollLock() on unmount (replace the direct document.body writes),
and do the same in navbar.tsx so both components coordinate the scroll lock.

---

Nitpick comments:
In `@src/app/`[locale]/(marketing)/contact/page.tsx:
- Around line 116-129: The company info section currently hardcodes company
details; replace those literals with the centralized constants from app.company
(use app.company.legal, app.company.stage, app.company.locations) so the bar
reads the single source of truth; import or reference the existing app.company
object where page.tsx renders the <section> and substitute the three spans
(legal, stage, locations) to use those properties while preserving the
separators and accessibility attributes.
- Line 11: The file imports Link from "next/link" and uses it to render external
URLs from the contactLinks array; replace those Link usages with plain anchor
elements (<a>) using target="_blank" and rel="noopener noreferrer" for safety,
and remove the unused Link import. Locate the contactLinks array and each JSX
rendering that references Link (around the block where Link is imported and the
map rendering lines ~85-103) and change to <a href={url} ...>text</a> while
preserving styling and accessibility attributes.

In `@src/features/marketing/sections/footer-cta.tsx`:
- Around line 66-70: The input and submit button are using hardcoded hex colors
(border `#4A5272` and button bg `#C13130`); update the className on the input
element and the button element in footer-cta.tsx to use the existing CSS design
tokens instead (e.g., replace border-[`#4A5272`] with the project border color
variable like border-border-muted or similar, and replace bg-[`#C13130`] with the
primary/accent button background token such as bg-accent-red or bg-primary) so
the component matches the other utility classes (bg-bg-card, text-text-primary)
and supports theming consistently.

In `@src/proxy.ts`:
- Line 107: The matcher currently hard-excludes routes with the negative
lookahead while shouldBypassProxy() also filters those same cases, causing
overlapping/unreachable branches; consolidate skip rules into one place by
removing the negative lookahead from matcher (change matcher to a broader
pattern, e.g. String.raw`/(.*)/` or the intended non-exclusion pattern) and
ensure shouldBypassProxy() contains the full canonical list of skip conditions
(api, trpc, _next, _vercel, files with extensions, etc.), or alternatively move
all skip logic into matcher and simplify shouldBypassProxy() to only
runtime-only checks — update the matcher and the shouldBypassProxy() function so
all skip rules live in the same single source of truth.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 121bb60c-40a6-4aea-8b4b-c82c96f100b8

📥 Commits

Reviewing files that changed from the base of the PR and between 732f7c3 and b0e5791.

⛔ Files ignored due to path filters (1)
  • public/assets/images/icon.svg is excluded by !**/*.svg
📒 Files selected for processing (118)
  • .git-hooks/commit-msg
  • .git-hooks/post-checkout
  • .git-hooks/post-merge
  • .git-hooks/pre-commit
  • .git-hooks/pre-push
  • .git-hooks/prepare-commit-msg
  • .github/labeler.yml
  • .github/labels.yml
  • .github/skills/feynman-auditor/SKILL.md
  • .github/skills/nemesis-auditor/SKILL.md
  • .github/skills/state-inconsistency-auditor/SKILL.md
  • .github/workflows/auto-merge.yml
  • .github/workflows/ci.yml
  • .github/workflows/deploy.yml
  • .github/workflows/label-sync.yml
  • .github/workflows/labeler.yml
  • .github/workflows/pr-size-labeler.yml
  • Dockerfile
  • agent-sync.sh
  • custom.d.ts
  • flake.nix
  • public/assets/models/README.md
  • scripts/lib/shell-utils.sh
  • scripts/setup.sh
  • src/actions/contact/submit.ts
  • src/app/[locale]/(marketing)/blog/[slug]/page.tsx
  • src/app/[locale]/(marketing)/blog/page.tsx
  • src/app/[locale]/(marketing)/contact/page.tsx
  • src/app/[locale]/(marketing)/layout.tsx
  • src/app/[locale]/(marketing)/legal/acceptable-use/page.tsx
  • src/app/[locale]/(marketing)/legal/cookie-policy/page.tsx
  • src/app/[locale]/(marketing)/legal/data-processing/page.tsx
  • src/app/[locale]/(marketing)/legal/layout.tsx
  • src/app/[locale]/(marketing)/legal/page.tsx
  • src/app/[locale]/(marketing)/legal/privacy/page.tsx
  • src/app/[locale]/(marketing)/legal/security/page.tsx
  • src/app/[locale]/(marketing)/legal/terms/page.tsx
  • src/app/[locale]/(marketing)/page.tsx
  • src/app/[locale]/(marketing)/platform/page.tsx
  • src/app/[locale]/(marketing)/protocol/page.tsx
  • src/app/[locale]/forbidden.tsx
  • src/app/[locale]/layout.tsx
  • src/app/[locale]/loading.tsx
  • src/app/_client/errors/401.tsx
  • src/app/_client/errors/403.tsx
  • src/app/_client/errors/404.tsx
  • src/app/_client/errors/500.tsx
  • src/app/_client/errors/503.tsx
  • src/app/_client/errors/error-layout.tsx
  • src/app/_client/errors/index.ts
  • src/app/_client/index.ts
  • src/app/_components/index.ts
  • src/app/_components/pagination/index.ts
  • src/app/_components/pagination/pagination.tsx
  • src/app/_components/vitals/index.ts
  • src/app/_components/vitals/web-vitals.tsx
  • src/app/_providers/index.ts
  • src/app/_providers/main-provider.tsx
  • src/app/api/[[...route]]/elysia.ts
  • src/app/api/[[...route]]/health/handlers.ts
  • src/app/api/[[...route]]/health/index.ts
  • src/app/api/[[...route]]/health/middleware.ts
  • src/app/api/[[...route]]/health/schema.ts
  • src/app/api/[[...route]]/route.ts
  • src/app/api/[[...route]]/status/handlers.ts
  • src/app/api/_elysia/constants/index.ts
  • src/app/api/_elysia/index.ts
  • src/app/api/_elysia/shared/auth.ts
  • src/app/api/_elysia/shared/config.ts
  • src/app/api/_elysia/shared/index.ts
  • src/app/api/_elysia/shared/middleware.ts
  • src/app/layout.tsx
  • src/app/manifest.ts
  • src/constants/app.ts
  • src/features/marketing/components/background.tsx
  • src/features/marketing/components/contact-form.tsx
  • src/features/marketing/components/footer.tsx
  • src/features/marketing/components/hash-scroll-center.tsx
  • src/features/marketing/components/hero-hud.tsx
  • src/features/marketing/components/hero-video-slideshow.tsx
  • src/features/marketing/components/locale-switcher.tsx
  • src/features/marketing/components/navbar.tsx
  • src/features/marketing/components/status-badge.tsx
  • src/features/marketing/components/use-case-tabs.tsx
  • src/features/marketing/sections/architecture-diagram.tsx
  • src/features/marketing/sections/competitive-table.tsx
  • src/features/marketing/sections/footer-cta.tsx
  • src/features/marketing/sections/hero.tsx
  • src/features/marketing/sections/how-it-works.tsx
  • src/features/marketing/sections/index.ts
  • src/features/marketing/sections/metrics-strip.tsx
  • src/features/marketing/sections/platform-pillars.tsx
  • src/features/marketing/sections/problem-statement.tsx
  • src/features/marketing/sections/technology.tsx
  • src/features/marketing/sections/trust-security.tsx
  • src/features/marketing/sections/use-cases.tsx
  • src/features/marketing/sections/why-now.tsx
  • src/hooks/use-in-view.ts
  • src/i18n/navigation.ts
  • src/i18n/request.ts
  • src/i18n/routing.ts
  • src/lib/better-stack.ts
  • src/lib/content.ts
  • src/lib/utils.test.ts
  • src/mdx-components.tsx
  • src/providers/core/events.tsx
  • src/providers/core/index.ts
  • src/providers/core/tailwind-indicator.tsx
  • src/providers/global/index.ts
  • src/providers/global/jotai-provider.tsx
  • src/providers/index.ts
  • src/providers/providers.tsx
  • src/proxy.ts
  • src/utils/helpers.test.ts
  • src/utils/helpers.ts
  • src/utils/logger.test.ts
  • src/utils/seo/meta.test.ts
  • src/utils/seo/speculation-schema.test.ts
✅ Files skipped from review due to trivial changes (82)
  • .github/labels.yml
  • flake.nix
  • .git-hooks/commit-msg
  • .github/workflows/auto-merge.yml
  • .github/skills/state-inconsistency-auditor/SKILL.md
  • .git-hooks/prepare-commit-msg
  • scripts/lib/shell-utils.sh
  • scripts/setup.sh
  • .git-hooks/pre-push
  • src/app/_components/pagination/index.ts
  • src/app/_client/index.ts
  • .git-hooks/post-checkout
  • .github/workflows/pr-size-labeler.yml
  • src/app/api/[[...route]]/health/schema.ts
  • Dockerfile
  • public/assets/models/README.md
  • src/features/marketing/components/background.tsx
  • .github/labeler.yml
  • .git-hooks/pre-commit
  • .github/workflows/labeler.yml
  • src/app/api/[[...route]]/elysia.ts
  • .github/skills/nemesis-auditor/SKILL.md
  • custom.d.ts
  • .github/workflows/label-sync.yml
  • src/app/_components/vitals/index.ts
  • .github/workflows/ci.yml
  • src/providers/index.ts
  • src/app/api/_elysia/constants/index.ts
  • .github/workflows/deploy.yml
  • src/app/api/[[...route]]/health/middleware.ts
  • src/features/marketing/components/status-badge.tsx
  • src/app/_client/errors/401.tsx
  • src/app/_providers/index.ts
  • src/app/_client/errors/503.tsx
  • src/app/_client/errors/error-layout.tsx
  • src/app/_components/pagination/pagination.tsx
  • src/app/api/_elysia/shared/index.ts
  • src/app/_providers/main-provider.tsx
  • src/app/_client/errors/500.tsx
  • src/app/_client/errors/403.tsx
  • src/providers/global/jotai-provider.tsx
  • src/features/marketing/components/use-case-tabs.tsx
  • .git-hooks/post-merge
  • src/app/api/_elysia/index.ts
  • src/app/api/[[...route]]/route.ts
  • src/app/_client/errors/index.ts
  • src/providers/global/index.ts
  • src/app/api/_elysia/shared/config.ts
  • src/app/api/[[...route]]/status/handlers.ts
  • src/providers/core/index.ts
  • agent-sync.sh
  • src/app/api/_elysia/shared/auth.ts
  • .github/skills/feynman-auditor/SKILL.md
  • src/app/api/_elysia/shared/middleware.ts
  • src/app/_components/vitals/web-vitals.tsx
  • src/providers/core/events.tsx
  • src/features/marketing/sections/how-it-works.tsx
  • src/hooks/use-in-view.ts
  • src/features/marketing/sections/index.ts
  • src/app/api/[[...route]]/health/handlers.ts
  • src/mdx-components.tsx
  • src/providers/providers.tsx
  • src/providers/core/tailwind-indicator.tsx
  • src/lib/utils.test.ts
  • src/app/[locale]/(marketing)/legal/acceptable-use/page.tsx
  • src/app/_client/errors/404.tsx
  • src/app/[locale]/(marketing)/legal/privacy/page.tsx
  • src/i18n/routing.ts
  • src/i18n/request.ts
  • src/app/api/[[...route]]/health/index.ts
  • src/app/[locale]/(marketing)/legal/terms/page.tsx
  • src/app/[locale]/(marketing)/layout.tsx
  • src/app/[locale]/(marketing)/protocol/page.tsx
  • src/app/[locale]/(marketing)/platform/page.tsx
  • src/features/marketing/sections/trust-security.tsx
  • src/utils/helpers.test.ts
  • src/app/[locale]/(marketing)/legal/page.tsx
  • src/utils/seo/speculation-schema.test.ts
  • src/features/marketing/sections/why-now.tsx
  • src/utils/seo/meta.test.ts
  • src/lib/better-stack.ts
  • src/app/_components/index.ts
🚧 Files skipped from review as they are similar to previous changes (22)
  • src/app/[locale]/forbidden.tsx
  • src/app/[locale]/(marketing)/legal/layout.tsx
  • src/app/[locale]/(marketing)/page.tsx
  • src/app/manifest.ts
  • src/actions/contact/submit.ts
  • src/i18n/navigation.ts
  • src/features/marketing/components/hash-scroll-center.tsx
  • src/app/[locale]/(marketing)/legal/cookie-policy/page.tsx
  • src/app/layout.tsx
  • src/features/marketing/components/contact-form.tsx
  • src/features/marketing/components/hero-video-slideshow.tsx
  • src/features/marketing/sections/use-cases.tsx
  • src/features/marketing/sections/competitive-table.tsx
  • src/constants/app.ts
  • src/features/marketing/sections/metrics-strip.tsx
  • src/app/[locale]/(marketing)/legal/data-processing/page.tsx
  • src/app/[locale]/(marketing)/legal/security/page.tsx
  • src/features/marketing/sections/platform-pillars.tsx
  • src/features/marketing/sections/technology.tsx
  • src/features/marketing/components/locale-switcher.tsx
  • src/utils/logger.test.ts
  • src/features/marketing/components/footer.tsx

Comment on lines +17 to +23
function formatDate(dateStr: string, locale: string) {
return new Date(dateStr).toLocaleDateString(locale, {
year: "numeric",
month: "long",
day: "numeric",
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Timezone drift when formatting YYYY-MM-DD dates.

Same issue as in the blog post page: new Date(dateStr) parses date-only strings as UTC midnight. In negative UTC offset timezones (e.g., US), toLocaleDateString may display the previous calendar day.

🛠️ Proposed fix
 function formatDate(dateStr: string, locale: string) {
-  return new Date(dateStr).toLocaleDateString(locale, {
+  const [year, month, day] = dateStr.split("-").map(Number);
+  const date = new Date(Date.UTC(year, month - 1, day));
+  return new Intl.DateTimeFormat(locale, {
     year: "numeric",
     month: "long",
     day: "numeric",
-  });
+    timeZone: "UTC",
+  }).format(date);
 }
📝 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
function formatDate(dateStr: string, locale: string) {
return new Date(dateStr).toLocaleDateString(locale, {
year: "numeric",
month: "long",
day: "numeric",
});
}
function formatDate(dateStr: string, locale: string) {
const [year, month, day] = dateStr.split("-").map(Number);
const date = new Date(Date.UTC(year, month - 1, day));
return new Intl.DateTimeFormat(locale, {
year: "numeric",
month: "long",
day: "numeric",
timeZone: "UTC",
}).format(date);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`[locale]/(marketing)/blog/page.tsx around lines 17 - 23, The
formatDate function suffers from timezone drift because new Date(dateStr) treats
YYYY-MM-DD as UTC midnight and negative offsets can render the previous day;
update formatDate to parse the dateStr (e.g., split/regex for year, month, day)
and construct the Date using Date.UTC with a safe midday time (e.g., new
Date(Date.UTC(year, month-1, day, 12, 0, 0))) before calling
toLocaleDateString(locale, ...); this change in the formatDate function prevents
local timezone offsets from shifting the displayed calendar day.

Comment on lines +24 to +50
function usePlatformItems() {
const t = useTranslations("Nav.platformItems");
return useMemo(
() => [
{
title: t("overview"),
href: "/platform",
description: t("overviewDesc"),
},
{
title: t("protocol"),
href: "/protocol",
description: t("protocolDesc"),
},
{
title: t("technology"),
href: "/platform#technology",
description: t("technologyDesc"),
},
{
title: t("useCases"),
href: "/platform#use-cases",
description: t("useCasesDesc"),
},
],
[t],
);
Copy link
Copy Markdown

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:

# First, let's examine the navbar.tsx file to understand the structure
wc -l src/features/marketing/components/navbar.tsx

Repository: resq-software/landing

Length of output: 113


🏁 Script executed:

# Read the entire navbar.tsx to understand the NavListItem and link handling
cat -n src/features/marketing/components/navbar.tsx

Repository: resq-software/landing

Length of output: 14704


🏁 Script executed:

# Search for how internal links are handled elsewhere in the codebase
rg -A 5 "from 'next/link'" src/features/marketing/components/

Repository: resq-software/landing

Length of output: 47


Desktop platform links should use Next.js Link for internal routes.

NavListItem always renders a raw <a> element for all routes. Platform items like /platform and /protocol bypass the app router, causing full-page navigations instead of client-side routing. The mobile menu correctly handles this (lines 316–343) by conditionally selecting between Link and <a>. Update NavListItem to follow the same pattern: use Link for non-external hrefs and <a> for external ones.

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

In `@src/features/marketing/components/navbar.tsx` around lines 24 - 50,
NavListItem currently always renders a raw <a>, causing internal links (e.g.,
items returned by usePlatformItems like "/platform" and "/protocol") to do
full-page navigations; update NavListItem to mirror the mobile menu logic by
importing Link from "next/link" and conditionally rendering <Link
href={href}>...children...</Link> for non-external/internal hrefs and falling
back to a plain <a href={href} ...> for external URLs, preserving existing props
(target, rel, className) and accessibility attributes; ensure any external
detection helper used in the mobile menu (or equivalent logic) is reused so
internal routes use client-side navigation.

Comment on lines 203 to +205
<nav
aria-label="Main navigation"
className="container flex h-16 items-center justify-between md:h-20"
className="container flex h-16 items-center justify-between px-6 py-2 md:h-20 md:py-3"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Localize the navigation landmark label too.

aria-label="Main navigation" stays English on every locale. Please move it into the Nav catalog with the other menu strings so screen readers get the translated label as well.

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

In `@src/features/marketing/components/navbar.tsx` around lines 203 - 205, The nav
element's aria-label is hardcoded ("Main navigation"); add a key (e.g.,
"mainNavigation") to the Nav i18n catalog with translated values and use the
same translation lookup used for other menu strings in this component (the
Nav/navbar component) to set aria-label dynamically; update the nav JSX to call
the translation function (the same helper used for other labels in this file)
and pass the localized string into aria-label so screen readers receive the
translated landmark label.

Comment on lines +303 to 361
{/* Full-screen mobile menu (Skydio-style) */}
<div
id="mobile-menu"
inert={!menuOpen}
className={cn(
"grid border-t border-border-default/50 bg-bg-primary/98 backdrop-blur-xl transition-all duration-300 lg:hidden",
"fixed inset-0 top-16 z-40 flex flex-col bg-bg-primary transition-all duration-300 md:top-20 lg:hidden",
menuOpen
? "grid-rows-[1fr] opacity-100"
: "grid-rows-[0fr] border-t-0 opacity-0",
? "visible translate-y-0 opacity-100"
: "invisible -translate-y-4 opacity-0",
)}
>
<div className="overflow-hidden">
<div className="container flex flex-col gap-1 py-4">
{MOBILE_LINKS.map((link) => (
<a
key={link.label}
href={link.href}
className="px-4 py-3 font-mono text-sm uppercase tracking-wider text-text-secondary transition-colors hover:text-text-primary"
onClick={() => setMenuOpen(false)}
{...(link.href.startsWith("http")
? { target: "_blank", rel: "noopener noreferrer" }
: {})}
>
{link.label}
{link.href.startsWith("http") && (
<span className="ml-1.5 text-text-muted">↗</span>
)}
</a>
))}
<div className="mt-3 border-t border-border-default pt-4">
<Link
href="/contact"
className="block w-full border border-accent-red bg-accent-red/10 px-5 py-3 text-center font-mono text-xs font-bold uppercase tracking-widest text-accent-red transition-colors hover:bg-accent-red/20"
onClick={() => setMenuOpen(false)}
>
Mission Control
</Link>
</div>
<div className="flex-1 overflow-y-auto px-6 py-6">
<div className="flex flex-col divide-y divide-border-default/30">
{mobileLinks.map((link) => {
const external = link.href.startsWith("http");
const El = external ? "a" : Link;
return (
<El
key={link.label}
href={link.href}
className="py-4 font-display text-xl font-bold uppercase tracking-wide text-text-primary transition-colors hover:text-accent-red sm:text-2xl"
onClick={() => setMenuOpen(false)}
{...(external
? { target: "_blank", rel: "noopener noreferrer" }
: {})}
>
{link.label}
{external && (
<>
<span
className="ml-2 text-text-muted"
aria-hidden="true"
>
</span>
<span className="sr-only"> {t("opensInNewTab")}</span>
</>
)}
</El>
);
})}
</div>

{/* Mobile CTA */}
<div className="mt-8 sm:hidden">
<Link
href="/contact"
className="block w-full border border-accent-red bg-accent-red/10 px-5 py-3.5 text-center font-mono text-xs font-bold uppercase tracking-widest text-accent-red transition-colors hover:bg-accent-red/20"
onClick={() => setMenuOpen(false)}
>
{t("missionControl")}
</Link>
</div>
</div>

{/* Mobile locale switcher at bottom */}
<div className="border-t border-border-default/30 px-6 py-6">
<LocaleSwitcher variant="full" />
</div>
Copy link
Copy Markdown

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:

# First, let's examine the navbar component and understand the full context
wc -l src/features/marketing/components/navbar.tsx

Repository: resq-software/landing

Length of output: 113


🏁 Script executed:

# Read the navbar component to see the full implementation
cat -n src/features/marketing/components/navbar.tsx | head -100

Repository: resq-software/landing

Length of output: 3162


🏁 Script executed:

# Check if there's an overlay or backdrop element before the mobile menu
cat -n src/features/marketing/components/navbar.tsx | sed -n '280,365p'

Repository: resq-software/landing

Length of output: 3989


🏁 Script executed:

# Check if Radix Dialog is in dependencies
fd -e json -e lock | xargs grep -l "radix-ui.*dialog\|@radix-ui/dialog" 2>/dev/null | head -5

Repository: resq-software/landing

Length of output: 73


🏁 Script executed:

# Search for Dialog usage in the codebase to understand patterns
rg -t tsx -t ts "Dialog|Sheet|Modal" src/ --max-count=20

Repository: resq-software/landing

Length of output: 92


🏁 Script executed:

# Search for Dialog/Modal/Sheet usage patterns in the codebase
rg "Dialog|Sheet|Modal" src/ --max-count=20

Repository: resq-software/landing

Length of output: 47


🏁 Script executed:

# Check the bun.lock file for Radix UI dependencies
grep -A 2 -B 2 "radix-ui" bun.lock | head -50

Repository: resq-software/landing

Length of output: 10758


🏁 Script executed:

# Check the package.json for dependencies
cat package.json | grep -A 20 '"dependencies"'

Repository: resq-software/landing

Length of output: 845


🏁 Script executed:

# Look for any custom focus trap or modal utilities
fd -e ts -e tsx src/lib src/utils | xargs rg -l "focus.*trap|modal|dialog" 2>/dev/null

Repository: resq-software/landing

Length of output: 439


🏁 Script executed:

# Check if there's a backdrop overlay element in the navbar
rg "backdrop|overlay" src/features/marketing/components/navbar.tsx

Repository: resq-software/landing

Length of output: 323


🏁 Script executed:

# Check the beginning of the navbar component to see the full structure
cat -n src/features/marketing/components/navbar.tsx | sed -n '100,200p'

Repository: resq-software/landing

Length of output: 3656


🏁 Script executed:

# Look for any backdrop/overlay element elsewhere in the navbar
cat -n src/features/marketing/components/navbar.tsx | sed -n '240,305p'

Repository: resq-software/landing

Length of output: 3204


🏁 Script executed:

# Check if there's any focus management or scroll prevention code
rg "useEffect|useFocusTrap|removeScroll|focusScope" src/features/marketing/components/navbar.tsx

Repository: resq-software/landing

Length of output: 198


🏁 Script executed:

# Verify Radix Dialog is properly available and check version
cat package.json | grep -i "@radix-ui/react-dialog"

Repository: resq-software/landing

Length of output: 47


🏁 Script executed:

# Check what's in `@resq-sw/ui` to understand custom component availability
cat package.json | grep "@resq-sw/ui"

Repository: resq-software/landing

Length of output: 93


🏁 Script executed:

# Look for any Sheet/Dialog implementations in src
find src -type f -name "*.tsx" -o -name "*.ts" | head -20

Repository: resq-software/landing

Length of output: 947


🏁 Script executed:

# Check if there are any custom modal/dialog utilities
fd -type f -path "*/src/lib/*" -o -path "*/src/components/*" | xargs rg "Dialog|Modal|Sheet" 2>/dev/null | head -20

Repository: resq-software/landing

Length of output: 235


🏁 Script executed:

# Verify the exact issue: check if there's proper focus trapping or background element rendering
cat -n src/features/marketing/components/navbar.tsx | tail -20

Repository: resq-software/landing

Length of output: 904


Replace the mobile menu with Radix Dialog for proper modal behavior and focus management.

The current implementation relies on inert={!menuOpen} which only disables interaction when the menu is closed. Once open, focus can escape to page elements behind the menu, breaking modal behavior for keyboard users. While body scroll locking and Escape key handling are present, there's no focus trapping to keep focus within the menu or prevent tabbing to background elements.

Use Radix Dialog primitive to provide built-in focus trapping, backdrop overlay, and proper modal semantics. This aligns with the coding guideline requiring Radix UI primitives for components in src/{components,features}/**/*.{ts,tsx}.

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

In `@src/features/marketing/components/navbar.tsx` around lines 303 - 361, Replace
the current manual mobile menu DOM (the div with id="mobile-menu" that uses
inert={!menuOpen}, menuOpen state, and setMenuOpen) with a Radix Dialog
primitive: wrap the menu in Dialog.Root open={menuOpen}
onOpenChange={setMenuOpen}, render the backdrop via Dialog.Overlay and the menu
contents inside Dialog.Content (or Dialog.Portal > Dialog.Content) so Radix
handles focus trapping and Escape/close behavior; remove the inert prop and
existing manual focus logic/escape handlers. Keep the existing mapping over
mobileLinks (including external link handling, target/rel, icon and sr-only
text), the mobile CTA Link (href="/contact") and LocaleSwitcher inside the
Dialog.Content, and import/use Dialog, DialogOverlay, DialogContent, DialogClose
from `@radix-ui/react-dialog` as needed. Ensure styling/classes from the original
container are applied to Dialog.Content and that setMenuOpen(false) is preserved
for link onClick to close the dialog.

>
<div className="overflow-x-auto">
<table className="w-full min-w-[500px]" aria-label="System architecture layers and technologies">
<table className="w-full min-w-125" aria-label="System architecture layers and technologies">
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search for min-w-125 definition in Tailwind config or globals.css

# Check if min-w-125 is defined as a custom utility or theme extension
rg -n "min-w-125|125:" src/styles/globals.css

# Also check any tailwind config files
fd -e js -e ts -e mjs "tailwind" --exec cat {} 2>/dev/null | head -100

Repository: resq-software/landing

Length of output: 47


🏁 Script executed:

#!/bin/bash
# More comprehensive search for min-w-125 and Tailwind config

# Search for min-w-125 across entire codebase
echo "=== Searching for min-w-125 in all files ==="
rg "min-w-125" .

# Search for all possible Tailwind config file patterns
echo "=== Searching for Tailwind config files ==="
fd "tailwind.config" .

# Check for Tailwind config in package.json
echo "=== Checking package.json for Tailwind config ==="
cat package.json | grep -A 20 "tailwind"

# Look for theme extensions or custom spacing in globals.css
echo "=== Viewing src/styles/globals.css ==="
head -100 src/styles/globals.css

# Search for 500px or similar sizing that min-w-125 might be replacing
echo "=== Searching for 500px references ==="
rg "500px" src/

Repository: resq-software/landing

Length of output: 4673


🏁 Script executed:

#!/bin/bash
# View complete globals.css file
cat src/styles/globals.css | tail -100

Repository: resq-software/landing

Length of output: 2586


Remove min-w-125 and use an arbitrary Tailwind value or define the custom class.

The class min-w-125 is not defined anywhere in src/styles/globals.css or in any Tailwind configuration file. This class is actively used in both architecture-diagram.tsx and technology.tsx, but since Tailwind's default spacing scale only goes up to 96 (384px), this custom value will not produce any CSS output and the table will not have the intended minimum width constraint.

Either revert to the arbitrary value min-w-[500px] or add min-w-125 to the Tailwind theme configuration in src/styles/globals.css within the @theme inline block.

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

In `@src/features/marketing/sections/architecture-diagram.tsx` at line 95, The
table element in the ArchitectureDiagram component uses a non-existent Tailwind
class "min-w-125" (seen in the table's className "w-full min-w-125"), which
produces no CSS; either replace that token with an arbitrary Tailwind value like
"min-w-[500px]" in the table's className (and likewise in technology.tsx where
used) or add "min-w-125" to your Tailwind theme scale inside the `@theme` inline
block in src/styles/globals.css so the class is generated.

Comment on lines +13 to +17
const SCENARIOS: HeroScenario[] = [
{ lat: "34.0522° N", lng: "118.2437° W", location: "Los Angeles, CA", meshStatus: "ACTIVE", uptimeBase: 99.8 },
{ lat: "29.7604° N", lng: "95.3698° W", location: "Houston, TX", meshStatus: "ACTIVE", uptimeBase: 99.6 },
{ lat: "37.5858° N", lng: "36.9371° E", location: "Kahramanmaraş, Turkey", meshStatus: "ACTIVE", uptimeBase: 99.4 },
];
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Defensively bound slideshow index before scenario lookup.

SCENARIOS[videoIndex] can become undefined if slideshow video count and scenario count drift, causing a runtime break in HeroHud when it reads scenario.uptimeBase.

💡 Proposed defensive fix
 export function Hero() {
   const [videoIndex, setVideoIndex] = useState(0);
+  const currentScenario = SCENARIOS[videoIndex] ?? SCENARIOS[0];

   return (
@@
-      <HeroVideoSlideshow onIndexChange={setVideoIndex} />
+      <HeroVideoSlideshow onIndexChange={(nextIndex) => setVideoIndex(nextIndex % SCENARIOS.length)} />
@@
-      <HeroHud scenario={SCENARIOS[videoIndex]} />
+      <HeroHud scenario={currentScenario} />

Also applies to: 30-30, 90-90

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

In `@src/features/marketing/sections/hero.tsx` around lines 13 - 17,
SCENARIOS[videoIndex] can be undefined causing HeroHud to read properties off
undefined; clamp or defensively resolve the scenario before lookup: compute an
index = Math.max(0, Math.min(videoIndex, SCENARIOS.length - 1)) or select a
fallback scenario when SCENARIOS[videoIndex] is undefined, then pass that safe
scenario into HeroHud and any other places referencing SCENARIOS[videoIndex]
(e.g., where videoIndex is used to read uptimeBase and location), and apply the
same defensive lookup for the other occurrences noted (the other SCENARIOS
access points).

Comment on lines +14 to +16
{ lat: "34.0522° N", lng: "118.2437° W", location: "Los Angeles, CA", meshStatus: "ACTIVE", uptimeBase: 99.8 },
{ lat: "29.7604° N", lng: "95.3698° W", location: "Houston, TX", meshStatus: "ACTIVE", uptimeBase: 99.6 },
{ lat: "37.5858° N", lng: "36.9371° E", location: "Kahramanmaraş, Turkey", meshStatus: "ACTIVE", uptimeBase: 99.4 },
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

New scenario text is hardcoded and bypasses localization.

The newly introduced location and meshStatus display values are English literals, which undercuts locale coverage for es/zh/hi/ar.

Comment on lines +59 to +62
export function getPostBySlug(slug: string): Post | null {
const filePath = path.join(POSTS_DIR, `${slug}.mdx`);

if (!fs.existsSync(filePath)) return null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Sanitize slug to prevent path traversal.

The slug parameter is used directly in path.join without validation. A malicious slug like ../../../etc/passwd could potentially read files outside POSTS_DIR. While this is likely only called with slugs from generateStaticParams, adding validation provides defense-in-depth.

🛡️ Proposed fix
 export function getPostBySlug(slug: string): Post | null {
+  // Prevent path traversal
+  if (slug.includes("..") || slug.includes("/") || slug.includes("\\")) {
+    return null;
+  }
+
   const filePath = path.join(POSTS_DIR, `${slug}.mdx`);

   if (!fs.existsSync(filePath)) return null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/content.ts` around lines 59 - 62, getPostBySlug currently uses the
incoming slug directly in path.join which allows path traversal; validate or
sanitize the slug before building filePath (e.g., enforce an allowlist regex
like only letters/numbers/hyphens/underscores or compute resolved =
path.resolve(POSTS_DIR, `${slug}.mdx`) and ensure
resolved.startsWith(path.resolve(POSTS_DIR))). If validation fails, return null
(or throw) and only then read the file; update references to getPostBySlug,
POSTS_DIR, and filePath accordingly.

Comment on lines +92 to 99
// Run i18n locale detection and routing first
const response = intlMiddleware(request);

// Apply security headers on top
const nonce = crypto.randomUUID().replaceAll("-", "");
applySecurityHeaders(response, nonce);
response.headers.set("x-nonce", nonce);
response.headers.set("x-url", pathname);
Copy link
Copy Markdown

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

🌐 Web query:

In Next.js App Router proxy/middleware, when using a nonce-based Content Security Policy, does Next.js need the nonce/CSP to be forwarded on the request for the current render, or is setting them only on the returned NextResponse sufficient?

💡 Result:

In Next.js App Router middleware (or proxy), for nonce-based CSP to work during server-side rendering of the current request, the CSP header containing the nonce must be forwarded on the request via NextResponse.next({ request: { headers: requestHeaders } }) where requestHeaders includes the Content-Security-Policy header with the nonce. Setting the CSP only on the returned NextResponse (response headers) is not sufficient for Next.js to extract and apply the nonce to its injected scripts/styles during rendering. Next.js parses the request's CSP header during SSR to automatically apply the nonce to its own script and link tags (as confirmed in official docs and multiple implementations). The response CSP header is needed for browser enforcement, so both are typically set. Example from official Next.js docs (middleware/proxy): const requestHeaders = new Headers(request.headers); requestHeaders.set('Content-Security-Policy', contentSecurityPolicyHeaderValue); // REQUIRED for render const response = NextResponse.next({ request: { headers: requestHeaders }, }); response.headers.set('Content-Security-Policy', contentSecurityPolicyHeaderValue); // For browser Without the request header, Next.js won't detect the nonce-'nonce-value' in CSP and scripts will lack the nonce attribute or have empty ones, breaking under strict CSP.

Citations:


Forward the nonce and CSP header in the request, not just the response.

For nonce-based CSP in Next.js App Router, the CSP header containing the nonce must be set on the forwarded request via NextResponse.next({ request: { headers: requestHeaders } }). Setting these headers only on the response is insufficient—Next.js parses the request's CSP header during SSR to inject the nonce into its own scripts and styles. Without the nonce in the request headers, Next.js will not apply the nonce attribute, breaking the CSP policy.

Update the code to forward the nonce and CSP on the request headers before calling intlMiddleware, or ensure applySecurityHeaders sets them on both the request and response appropriately.

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

In `@src/proxy.ts` around lines 92 - 99, The code sets the nonce and CSP only on
the response, but Next.js requires the CSP header (with nonce) on the forwarded
request so SSR can inject nonce attributes; update the flow around
intlMiddleware and applySecurityHeaders to add the nonce and CSP into the
outgoing request headers (e.g., build or mutate requestHeaders and call
NextResponse.next({ request: { headers: requestHeaders } })) rather than only
setting them on the response, or ensure applySecurityHeaders writes both request
and response headers; modify where nonce is generated (keep using
crypto.randomUUID().replaceAll("-", "")) and ensure the same nonce is applied to
the CSP header and forwarded on the request before
intlMiddleware/NextResponse.next is invoked.

Comment on lines 75 to +78
const envBaseUrl =
process?.env?.['VITE_BASE_URL'] ||
process?.env?.['NEXT_PUBLIC_BASE_URL'] ||
process?.env?.['BASE_URL'];
process?.env?.VITE_BASE_URL ||
process?.env?.NEXT_PUBLIC_BASE_URL ||
process?.env?.BASE_URL;
Copy link
Copy Markdown

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:

# Check if src/env.ts exists and examine its structure
fd -t f "env\.(ts|js)" src/ | head -20

Repository: resq-software/landing

Length of output: 75


🏁 Script executed:

# Get the full content of src/utils/helpers.ts to understand context
wc -l src/utils/helpers.ts

Repository: resq-software/landing

Length of output: 89


🏁 Script executed:

# Examine the env.ts file if it exists
cat src/env.ts 2>/dev/null || echo "File not found"

Repository: resq-software/landing

Length of output: 2310


🏁 Script executed:

# Check the full content of src/utils/helpers.ts around lines 65-78
sed -n '1,85p' src/utils/helpers.ts | cat -n

Repository: resq-software/landing

Length of output: 3778


🏁 Script executed:

# Search for usage of VITE_BASE_URL, BASE_URL, NEXT_PUBLIC_BASE_URL in codebase
rg "VITE_BASE_URL|BASE_URL|NEXT_PUBLIC_BASE_URL" --type ts --type tsx --type js --type jsx -n

Repository: resq-software/landing

Length of output: 92


🏁 Script executed:

# Check if helpers.ts imports from src/env.ts
grep -n "import.*env" src/utils/helpers.ts

Repository: resq-software/landing

Length of output: 47


🏁 Script executed:

# Search for how getURL is being used in the codebase
rg "getURL" --type ts --type tsx --type js --type jsx -B 2 -A 2

Repository: resq-software/landing

Length of output: 92


🏁 Script executed:

# Search for usage of VITE_BASE_URL, BASE_URL, NEXT_PUBLIC_BASE_URL in codebase without type restriction
rg "VITE_BASE_URL|BASE_URL|NEXT_PUBLIC_BASE_URL" -n

Repository: resq-software/landing

Length of output: 634


🏁 Script executed:

# Check if helpers.ts imports from src/env.ts
cat src/utils/helpers.ts | grep -i "import"

Repository: resq-software/landing

Length of output: 99


🏁 Script executed:

# Search for how getURL is being used
rg "getURL" -B 2 -A 2

Repository: resq-software/landing

Length of output: 4030


Environment variables must be validated through src/env.ts, not accessed directly from process.env.

Lines 76–78 access process?.env directly, which violates the project guideline requiring all environment variables to be validated via @t3-oss/env-nextjs. Additionally, optional chaining on an undeclared process identifier can still throw a ReferenceError in non-Node.js environments before the optional chain takes effect.

Add VITE_BASE_URL, NEXT_PUBLIC_BASE_URL, and BASE_URL to the schema in src/env.ts, then import and use the validated env export instead:

Suggested fix
-import { logger } from "./logger";
+import { logger } from "./logger";
+import { env } from "@/env";
-    const envBaseUrl =
-      process?.env?.VITE_BASE_URL ||
-      process?.env?.NEXT_PUBLIC_BASE_URL ||
-      process?.env?.BASE_URL;
+    const envBaseUrl =
+      env.NEXT_PUBLIC_BASE_URL || env.NEXT_PUBLIC_APP_URL;

Also update src/env.ts to include these variables in the clientEnvSchema.

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

In `@src/utils/helpers.ts` around lines 75 - 78, The code reads VITE_BASE_URL /
NEXT_PUBLIC_BASE_URL / BASE_URL directly from process.env (and uses optional
chaining on process) which violates the env validation rule; add VITE_BASE_URL,
NEXT_PUBLIC_BASE_URL, and BASE_URL to the clientEnvSchema in src/env.ts (so they
are validated and exposed on the client as needed), export the validated env,
then replace the direct process?.env access in the helper with the imported
validated env (e.g., use env.VITE_BASE_URL || env.NEXT_PUBLIC_BASE_URL ||
env.BASE_URL) and remove the optional chaining on process.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants