Skip to content

Redesign landing page#158

Open
pavsoss wants to merge 21 commits into
TENET-DEV-AI:mainfrom
pavsoss:redesign-landing-page
Open

Redesign landing page#158
pavsoss wants to merge 21 commits into
TENET-DEV-AI:mainfrom
pavsoss:redesign-landing-page

Conversation

@pavsoss

@pavsoss pavsoss commented Jun 10, 2026

Copy link
Copy Markdown

Summary

Upgrade of the TENET AI landing page to v2. This includes modularizing the monolithic App.tsx layout into a clean, reusable component architecture, consolidating all styles into a single stylesheet, adding new dynamic sections (an interactive sandbox, real-time terminal logs, and system architecture flow), and replacing all plain text symbols/emojis with premium vector SVG icons.

Key Changes

  • Modular Component Structure: Split the monolithic App.tsx into decoupled components under landing/src/components (including Hero, Navbar, Footer, TerminalPanel, DemoSection, ComparisonTable, BreachCards, StatsSection, PipelineSection, ArchitectureDiagram, and TenetLogo).
  • Styles Consolidation: Extracted all inline styles and consolidated them into landing/src/index.css using the locked color palette, full support for prefers-reduced-motion options, and skip-to-main accessibility links.
  • Pixel-perfect Comparison Table Alignment: Configured .compare-table to use table-layout: fixed and set proportional column percentage widths to align column cells and headers perfectly.
  • Smooth Streaming Log Animations: Configured the React map keys of TerminalPanel.tsx logs to use persistent unique IDs (line.id), which ensures only new arriving log rows trigger the slide-in/fade-in entrance transition.
  • Dynamic Stars & Forks: Integrated GitHub repository API calls to fetch stargazers and forks counts, rendering them as precise integers for counts under 1000 and adding a new forks button to the navigation bar.
  • Topbar Navigation & Responsive Spacing: Extended navbar links to cover all 8 landing page sections, and implemented a custom viewport media query (1181px - 1380px) to shrink padding and hide icons to prevent topbar layout collisions.
  • Vector Symbol Cleanup: Replaced play indicators, back-to-top symbols, CTA checkmarks, and shield emojis with custom inline SVGs or CSS-drawn border ticks.

Related Issue

Fixes #121


Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • CI/CD Improvement
  • Added Tests

Screenshots

image image image image image image image

Checklist

  • My code follows the project's code style
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Additional Notes (Optional)

  • Build targets and bundles compile successfully with zero errors or warnings (dist/index.html, dist/assets/index.css, dist/assets/index.js).
  • The repository was committed across 9 sequential commits to maintain a clean git history.

Summary by cubic

Redesigned the TENET landing page (v2) with a modular React layout, interactive demo, and unified, accessible styling. Latest updates add SEO/OG metadata, responsive polish, a restored footer with a Privacy Policy, and smoother stats/nav behavior.

  • New Features

    • Modular components: hero, navbar, demo (multi-attack presets + interactive pie chart), terminal logs, features, comparison, breach timeline, stats, architecture/pipeline, install, CTA, footer.
    • Interactive sandbox and visuals: streaming terminal logs, animated stats with sparklines, and topology diagrams.
    • Nav and footer: section highlighting, GitHub stars/forks, mobile menu scroll lock + Escape to close, back-to-top, Privacy Policy link.
    • Install cards with copy-to-clipboard; expanded SEO/Open Graph tags in landing/index.html.
  • Refactors

    • Unified global styles in landing/src/index.css with accessible defaults, prefers-reduced-motion, improved contrast/focus, and refined breakpoints; imported in landing/src/main.tsx.
    • Replaced emojis/bitmaps with inline SVGs; fixed comparison table layout with table-layout: fixed.
    • Updated .gitignore to ignore frontend artifacts (node_modules, dist, package-lock.json).

Written for commit ce9fdd6. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Redesigned landing page with new sections: performance metrics dashboard, interactive security demo, feature comparison table, threat intelligence timeline, and platform installation guides
    • Enhanced search engine visibility with improved metadata
  • Style

    • Added comprehensive responsive design with animations and mobile support
  • Chores

    • Updated build configuration patterns for Node.js and development tools

@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

@pavsoss is attempting to deploy a commit to the s3dfx-cyber's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Complete landing page redesign converting a monolithic ~656-line App into 14 reusable React components with dedicated sections (hero, stats, features, demo, comparison, architecture, breach timeline, install, CTA, footer), comprehensive CSS styling system with animations and responsive breakpoints, enhanced HTML metadata for SEO, and GitHub API integration for live stats display.

Changes

Landing Page Redesign & Component Architecture

Layer / File(s) Summary
Foundation, Configuration & App Refactor
.gitignore, landing/index.html, landing/src/App.tsx, landing/src/main.tsx
Updated .gitignore for Node.js/Frontend, enriched HTML with OG/Twitter metadata, refactored App from 656 to 43 lines by extracting 13 section components into composition layer, and wired global CSS import.
Navigation, Header & Mobile Menu
landing/src/components/Navbar.tsx
Navbar manages active section highlighting via IntersectionObserver, fetches GitHub stars/forks, locks body scroll during mobile menu, handles Escape key to close menu, and provides smooth-scroll navigation with desktop nav + hamburger mobile overlay.
Hero Section & Live Terminal Dashboard
landing/src/components/Hero.tsx, landing/src/components/TerminalPanel.tsx
Hero renders animated background layers, headline with cursor, CTA buttons, and trust pills. TerminalPanel simulates live security log feed with timed entry prepends, tag coloring, flash effects, and animated distribution bars refreshed every 1.4s.
Stats Section with Animated Counters & Sparklines
landing/src/components/StatsSection.tsx
AnimNum animates numeric values via cubic ease-out easing on viewport intersection with requestAnimationFrame; FadeUp reveals cards with staggered delays; sparkline SVG charts show per-stat trends.
Features Grid with Staggered Reveals
landing/src/components/FeaturesSection.tsx
Renders feature cards in responsive grid with icon, title, description, and color-mapped tag pairs; uses FadeUp to animate on viewport intersection with per-index delays.
Interactive Demo & Attack Scenario Visualization
landing/src/components/DemoSection.tsx, landing/src/components/InteractivePieChart.tsx
DemoSection types attack prompts character-by-character, supports ArrowUp/ArrowDown keyboard navigation, shows verdict panel with expandable bullet explanations, and clipboard copy state. InteractivePieChart renders 3D pie with arc geometry, hover/click handlers, and active-slice transforms.
Feature Comparison Table
landing/src/components/ComparisonTable.tsx
Renders tri-state capability matrix (yes/no/partial) with color-coded SVG status icons and labels; includes overall protection rating summary row with three progress bars.
Architecture Diagram & Pipeline Visualization
landing/src/components/UnifiedArchitectureSection.tsx
Renders SVG system topology diagram with styled boxes, arrows, and labels; includes "STEP 4 Learn" pipeline section with threat-intel/retraining/analyst-feedback/stronger-tomorrow cards.
Threat Intelligence Timeline with Expandable Responses
landing/src/components/BreachCards.tsx
Renders vertical threat timeline with severity badges, type/date metadata, and expandable response panels wired with aria-expanded/aria-controls; uses IntersectionObserver to set spine height and FadeUp for per-item reveal animations.
Installation Commands & Platform Selection
landing/src/components/InstallSection.tsx
Renders platform-specific install command tabs with click-to-copy feedback, platform-specific integration code examples in right-side panel, and FadeUp animation helper with staggered delays.
Call-to-Action Section with Chips
landing/src/components/CtaSection.tsx
Renders download CTA and GitHub link with smooth-scroll handler; includes informational chip badges mapped from static array and FadeUp animation wrapper.
Footer, Back-to-Top & Logo Components
landing/src/components/Footer.tsx, landing/src/components/TenetLogo.tsx
Footer tracks scroll position and shows/hides back-to-top button; renders brand links, smooth-scroll anchors, and a11y announcements. TenetLogo provides reusable parameterized SVG cube with gradients.
Global CSS Styling System
landing/src/index.css
Comprehensive stylesheet with global resets, design tokens, layout utilities, component styling, keyframe animations (pulse/fade/slide/blink/packet-travel), accessibility overrides, and responsive breakpoints for tablet/mobile/extra-small screens.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

The PR introduces significant structural changes across 14 new component files plus comprehensive styling, with heterogeneous logic spanning animations, interactive state management, accessibility features, and responsive design. While individual components are self-contained, the breadth of changes across multiple domains (navigation, stateful demos, terminal simulation, clipboard handling, scroll tracking, IntersectionObserver patterns) and dense CSS with multiple responsive breakpoints requires careful review of integration, accessibility compliance, and visual consistency across all screen sizes.

A rabbit hops through a redesigned warren,
With colorful walls and security so bold,
Animated shields and threats on the timeline,
Each component a room, each piece coming alive—
A landing page fortress, now gleaming and fine! 🛡️✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Redesign landing page' is concise and directly describes the primary change—a complete redesign of the landing page with new components, styling, and interactivity.
Linked Issues check ✅ Passed The PR successfully addresses issue #121 objectives: modular hero section, feature showcase (FeaturesSection, ComparisonTable), interactive demo (DemoSection), social proof (StatsSection, BreachCards), CTAs (CtaSection), responsive design, accessibility (skip-to-main, aria labels), and modern styling in index.css.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the landing page redesign: modularized components, unified styling, updated metadata, .gitignore updates for frontend artifacts, and a new stylesheet—all aligned with issue #121 objectives.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering all required sections of the template with detailed key changes, related issue, type of change, screenshots, and checklist.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

5 issues found across 19 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="landing/src/components/TenetLogo.tsx">

<violation number="1" location="landing/src/components/TenetLogo.tsx:7">
P2: Hardcoded SVG gradient IDs (`tg1`, `tg2`) in a reusable component cause duplicate-ID errors when multiple `TenetLogo` instances are rendered on the same page.</violation>
</file>

<file name="landing/src/components/DemoSection.tsx">

<violation number="1" location="landing/src/components/DemoSection.tsx:117">
P2: Clipboard copy action shows 'Copied!' even when the clipboard write fails because the Promise is not awaited or handled.</violation>
</file>

<file name="landing/src/components/Footer.tsx">

<violation number="1" location="landing/src/components/Footer.tsx:17">
P2: Back-to-top button visibility is not initialized from current scroll position on mount, so it remains hidden on scroll-restored or hash-linked page loads until the next scroll event.</violation>
</file>

<file name="landing/src/components/TerminalPanel.tsx">

<violation number="1" location="landing/src/components/TerminalPanel.tsx:33">
P2: Nested setTimeout callbacks are not cleared in the effect cleanup, allowing state updates after unmount. The 300ms processing timeout and the 600ms flash-red timeout are both created without storing their IDs, so only the interval is cleared on cleanup. Store timeout references and clear them alongside the interval.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

}
};

window.addEventListener('scroll', handleScroll);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Back-to-top button visibility is not initialized from current scroll position on mount, so it remains hidden on scroll-restored or hash-linked page loads until the next scroll event.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At landing/src/components/Footer.tsx, line 17:

<comment>Back-to-top button visibility is not initialized from current scroll position on mount, so it remains hidden on scroll-restored or hash-linked page loads until the next scroll event.</comment>

<file context>
@@ -0,0 +1,81 @@
+      }
+    };
+
+    window.addEventListener('scroll', handleScroll);
+    return () => window.removeEventListener('scroll', handleScroll);
+  }, []);
</file context>

Comment thread landing/src/components/TerminalPanel.tsx Outdated
const [bars, setBars] = useState([78, 14, 6, 2]); // distribution percentages

useEffect(() => {
const interval = setInterval(() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Nested setTimeout callbacks are not cleared in the effect cleanup, allowing state updates after unmount. The 300ms processing timeout and the 600ms flash-red timeout are both created without storing their IDs, so only the interval is cleared on cleanup. Store timeout references and clear them alongside the interval.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At landing/src/components/TerminalPanel.tsx, line 33:

<comment>Nested setTimeout callbacks are not cleared in the effect cleanup, allowing state updates after unmount. The 300ms processing timeout and the 600ms flash-red timeout are both created without storing their IDs, so only the interval is cleared on cleanup. Store timeout references and clear them alongside the interval.</comment>

<file context>
@@ -0,0 +1,165 @@
+  const [bars, setBars] = useState([78, 14, 6, 2]); // distribution percentages
+
+  useEffect(() => {
+    const interval = setInterval(() => {
+      // 1. Start processing animation for 300ms
+      setIsProcessing(true);
</file context>

return (
<svg width={size} height={size} viewBox="0 0 80 80" fill="none">
<defs>
<linearGradient id="tg1" x1="0%" y1="0%" x2="100%" y2="100%">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: Hardcoded SVG gradient IDs (tg1, tg2) in a reusable component cause duplicate-ID errors when multiple TenetLogo instances are rendered on the same page.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At landing/src/components/TenetLogo.tsx, line 7:

<comment>Hardcoded SVG gradient IDs (`tg1`, `tg2`) in a reusable component cause duplicate-ID errors when multiple `TenetLogo` instances are rendered on the same page.</comment>

<file context>
@@ -0,0 +1,45 @@
+  return (
+    <svg width={size} height={size} viewBox="0 0 80 80" fill="none">
+      <defs>
+        <linearGradient id="tg1" x1="0%" y1="0%" x2="100%" y2="100%">
+          <stop offset="0%" stopColor="#00e5ff" />
+          <stop offset="100%" stopColor="#0099cc" />
</file context>

Comment thread landing/src/components/DemoSection.tsx Outdated
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":401,"request":{"method":"PATCH","url":"https://api.github.com/repos/TENET-DEV-AI/TENET-AI/issues/comments/4671728864","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- review_stack_entry_start -->\n\n[![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/TENET-DEV-AI/TENET-AI/pull/158?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)\n\n<!-- review_stack_entry_end -->\n<!-- walkthrough_start -->\n\n<details>\n<summary>📝 Walkthrough</summary>\n\n## Walkthrough\n\nA complete landing page redesign extracted the original monolithic App implementation into 13 specialized React components (Navbar, Hero, TerminalPanel, StatsSection, PipelineSection, FeaturesSection, DemoSection, ComparisonTable, ArchitectureDiagram, BreachCards, InstallSection, CtaSection, Footer, TenetLogo) paired with a comprehensive CSS stylesheet, updated HTML metadata, and project configuration changes.\n\n## Changes\n\n**Landing Page Redesign**\n\n|Layer / File(s)|Summary|\n|---|---|\n|**Foundation & Configuration** <br> `.gitignore`, `landing/index.html`, `landing/src/App.tsx`, `landing/src/main.tsx`|Added Node.js/frontend ignores, expanded SEO metadata and favicon, refactored App from self-contained logic to composition layer that imports and renders 13 section components in a main layout with a skip-to-main accessibility link.|\n|**Navigation & Header Bar** <br> `landing/src/components/Navbar.tsx`|Navbar component with active section tracking via IntersectionObserver, mobile menu with Escape-key close, GitHub API calls for stars/forks, keyboard shortcut (/) to jump to primary CTA, smooth-scroll navigation for desktop/mobile, and body scroll locking.|\n|**Hero Section & Live Terminal** <br> `landing/src/components/Hero.tsx`, `landing/src/components/TerminalPanel.tsx`|Hero renders animated background layers, headline, CTA buttons (with smooth-scroll handler), and trust pills; TerminalPanel simulates live security log feed with timed entry additions, tag-based row coloring, conditional \"ANALYZING\" status, summary stats, and attack-type distribution bars.|\n|**Stats & Pipeline Architecture** <br> `landing/src/components/StatsSection.tsx`, `landing/src/components/PipelineSection.tsx`|StatsSection animates numeric counters from 0 to target on viewport intersection with sparkline charts; PipelineSection toggles between ALLOW/BLOCK packet states every 3 seconds with animated node emphasis and renders step-card grid with hover-triggered risk score bar.|\n|**Feature Cards & Comparison Table** <br> `landing/src/components/FeaturesSection.tsx`, `landing/src/components/ComparisonTable.tsx`|FeaturesSection displays feature cards with staggered fade-in animations and color-mapped tags; ComparisonTable renders tri-state capability matrix (yes/no/partial) with inline SVG status icons and overall protection rating progress visualization.|\n|**Interactive Demo & Architecture Diagram** <br> `landing/src/components/DemoSection.tsx`, `landing/src/components/ArchitectureDiagram.tsx`|DemoSection provides interactive attack-scenario selector with character-by-character typing animation, copy-to-clipboard, keyboard navigation, and expandable explanation bullets; ArchitectureDiagram shows animated packet switching between BLOCK and ALLOW routes based on 4-second cycle.|\n|**Threat Intelligence Timeline** <br> `landing/src/components/BreachCards.tsx`|BreachCards renders vertical timeline of breach case studies with expandable response panels, severity/type badges, animated spine height on viewport entry, and reusable FadeUp observer-driven animation wrapper.|\n|**Installation & CTA Sections** <br> `landing/src/components/InstallSection.tsx`, `landing/src/components/CtaSection.tsx`|InstallSection provides per-platform install commands with click-to-copy clipboard integration and feature lists; CtaSection renders download link with smooth-scroll handler, GitHub link, and informational chip badges.|\n|**Footer & Shared Utilities** <br> `landing/src/components/Footer.tsx`, `landing/src/components/TenetLogo.tsx`|Footer renders brand links, smooth-scroll \"Protected by\" anchor, fixed back-to-top button with scroll threshold detection, and screen-reader announcement; TenetLogo provides reusable scalable SVG cube component.|\n|**Complete CSS Styling System** <br> `landing/src/index.css`|Comprehensive stylesheet (584 lines) with global theme variables, resets, layout utilities, typography helpers, component styling for all sections, keyframe animations (pulse, fades, packet travel, blink), accessibility (prefers-reduced-motion override), and responsive breakpoints (desktop, tablet landscape/portrait, mobile, extra-small).|\n\n## Estimated code review effort\n\n🎯 4 (Complex) | ⏱️ ~60 minutes\n\n## Possibly related issues\n\n- **TENET-DEV-AI/TENET-AI#121**: Landing page redesign implementation that introduces new components, styling, and structure—directly aligns with this PR's extraction of monolithic App logic into specialized section components and comprehensive CSS system.\n\n## Poem\n\n> 🐇 *A rabbit hops through redesign dreams,*  \n> *Components spring where monolith once schemes,*  \n> *With animations bright and colors bold,*  \n> *The landing page's new story unfolds,*  \n> *Navbar, hero, stats—a symphony of sight,*  \n> *Styled with CSS and interactions right!*\n\n</details>\n\n<!-- walkthrough_end -->\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 4 | ❌ 1</summary>\n\n### ❌ Failed checks (1 warning)\n\n|     Check name     | Status     | Explanation                                                                          | Resolution                                                                         |\n| :----------------: | :--------- | :----------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- |\n| Docstring Coverage | ⚠️ Warning | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |\n\n<details>\n<summary>✅ Passed checks (4 passed)</summary>\n\n|         Check name         | Status   | Explanation                                                                                                                                                                                                                                                                  |\n| :------------------------: | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n|         Title check        | ✅ Passed | The title 'Redesign landing page' directly and accurately summarizes the main change: a comprehensive redesign of the landing page from a monolithic structure into a modular component-based architecture with new features and consolidated styling.                       |\n|      Description check     | ✅ Passed | The PR description is comprehensive and well-structured, including a detailed summary, key changes list, related issue, type of change selection, screenshots, testing notes, and a completed checklist. All major template sections are addressed with substantive content. |\n|     Linked Issues check    | ✅ Passed | Check skipped because no linked issues were found for this pull request.                                                                                                                                                                                                     |\n| Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request.                                                                                                                                                                                                     |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=TENET-DEV-AI/TENET-AI&utm_content=158)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n<!-- internal state start -->\n\n\n<!-- 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 -->\n\n<!-- internal state end -->"},"request":{"retryCount":1,"signal":{}}},"response":{"url":"https://api.github.com/repos/TENET-DEV-AI/TENET-AI/issues/comments/4671728864","status":401,"headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset","connection":"close","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Wed, 10 Jun 2026 15:29:00 GMT","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"github.com","strict-transport-security":"max-age=31536000; includeSubdomains; preload","vary":"Accept-Encoding, Accept, X-Requested-With","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-media-type":"github.v3; format=json","x-github-request-id":"381A:3F25E0:4CAB4A1:121316F2:6A2982BC","x-xss-protection":"0"},"data":{"message":"Requires authentication","documentation_url":"https://docs.github.com/rest","status":"401"}}}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 18

🧹 Nitpick comments (15)
landing/src/App.tsx (1)

27-27: ⚡ Quick win

Make skip-link target reliably focusable.

Line 27 should add tabIndex={-1} so keyboard focus consistently lands on the main region after using the skip link.

Suggested diff
-      <main id="main-content" className="main">
+      <main id="main-content" className="main" tabIndex={-1}>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/App.tsx` at line 27, The main element with id "main-content" in
App.tsx isn't reliably focusable after using the skip link; update the JSX for
the <main id="main-content" className="main"> element to include tabIndex={-1}
so keyboard focus can be programmatically moved to it (ensure the attribute is
added to the main element in the App component).
landing/src/components/Navbar.tsx (1)

203-207: ⚡ Quick win

Expose active nav item via aria-current.

Lines 203-207 and 290-294 currently communicate active state visually only. Add aria-current="page" for the active link in desktop and mobile menus.

Suggested diff
             <a
               key={item.id}
               href={`#${item.id}`}
               className={`nav-link ${activeSection === item.id ? 'active' : ''}`}
+              aria-current={activeSection === item.id ? 'page' : undefined}
               onClick={(e) => handleLinkClick(e, item.id)}
             >
             <a
               key={item.id}
               href={`#${item.id}`}
               className={`mobile-menu-link ${activeSection === item.id ? 'active' : ''}`}
+              aria-current={activeSection === item.id ? 'page' : undefined}
               onClick={(e) => handleLinkClick(e, item.id)}
             >

Also applies to: 290-294

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/Navbar.tsx` around lines 203 - 207, Navbar currently
indicates the active section only visually; update the anchor elements that
render navigation items (the desktop link loop around the JSX using
key={item.id}, href={`#${item.id}`} and className based on activeSection) and
the corresponding mobile menu anchors (the similar block around lines 290-294)
to include aria-current="page" when activeSection === item.id; i.e.,
conditionally add aria-current="page" for the active link in both desktop and
mobile render paths so screen readers receive the active state (no other
behavior changes required—leave handleLinkClick, activeSection logic intact).
.gitignore (1)

207-210: ⚡ Quick win

Keep frontend lockfiles versioned for reproducible installs.

Line 210 ignores package-lock.json; for npm-based app builds this makes dependency resolution non-deterministic across environments.

Suggested diff
 # Node.js / Frontend
 node_modules/
 dist/
-package-lock.json
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.gitignore around lines 207 - 210, The .gitignore currently excludes
package-lock.json which prevents committing the npm lockfile and breaks
reproducible installs; edit .gitignore to stop ignoring package-lock.json
(remove the "package-lock.json" entry) so the project's npm lockfile is
versioned alongside sources and ensures deterministic dependency resolution.
landing/src/components/Hero.tsx (1)

74-74: ⚡ Quick win

Consider moving inline style to CSS.

The PR objectives mention consolidating "all inline styles into landing/src/index.css." This SVG has style={{ marginRight: '6px' }} which could be moved to the .hero-pill-check selector for consistency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/Hero.tsx` at line 74, The SVG in Hero.tsx uses an
inline style (style={{ marginRight: '6px' }}) which should be moved to the
global stylesheet; add the rule .hero-pill-check { margin-right: 6px; } (or
adjust the existing .hero-pill-check selector in landing/src/index.css) and
remove the style prop from the SVG element in the Hero component so styling is
consolidated into the CSS file.
landing/src/components/DemoSection.tsx (1)

192-192: 💤 Low value

Consider renaming CSS class for component independence.

The class name hero-cursor suggests coupling to the Hero component. For better component independence and maintainability, consider using a more generic name like typing-cursor or blink-cursor.

-{status === 'typing' && <span className="hero-cursor">|</span>}
+{status === 'typing' && <span className="typing-cursor">|</span>}

Then update the CSS class name accordingly in landing/src/index.css.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/DemoSection.tsx` at line 192, Rename the CSS class
used for the typing cursor to a more component-agnostic name and update the
stylesheet reference: change the span in DemoSection.tsx where status ===
'typing' currently uses className "hero-cursor" to a generic name like
"typing-cursor" (or "blink-cursor") and then update the corresponding rule in
landing/src/index.css from .hero-cursor to the new class name so the component
is decoupled from Hero-specific styling.
landing/src/components/ArchitectureDiagram.tsx (2)

56-80: ⚡ Quick win

Move inline CSS to stylesheet for better separation of concerns.

Using dangerouslySetInnerHTML to inject CSS is non-idiomatic and flagged by static analysis. While the hardcoded content poses no XSS risk, this pattern couples component logic with styling. Move these keyframes and classes to landing/src/index.css for better maintainability.

♻️ Recommended refactor

Remove the <style> block from the SVG <defs>:

  <defs>
    <marker id="arrow" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
      <path d="M 0 1 L 10 5 L 0 9 z" fill="var(--border2)" />
    </marker>
    
    <linearGradient id="glow-cyan" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%" stopColor="var(--cyan)" />
      <stop offset="100%" stopColor="`#0088aa`" />
    </linearGradient>
    <linearGradient id="glow-purple" x1="0%" y1="0%" x2="100%" y2="100%">
      <stop offset="0%" stopColor="var(--purple)" />
      <stop offset="100%" stopColor="`#8b5cf6`" />
    </linearGradient>
-
-   {/* Animated dasharray definitions */}
-   <style dangerouslySetInnerHTML={{ __html: `
-     .anim-path {
-       stroke: var(--border2);
-       stroke-width: 1.5;
-       stroke-dasharray: 6 4;
-       animation: dash 30s linear infinite;
-     }
-     .anim-path-active {
-       stroke: var(--cyan);
-       stroke-width: 2;
-       filter: drop-shadow(0 0 2px var(--cyan));
-     }
-     `@keyframes` dash {
-       to {
-         stroke-dashoffset: -1000;
-       }
-     }
-     .arch-node {
-       transition: all 0.3s;
-     }
-     .arch-node:hover {
-       filter: drop-shadow(0 0 4px var(--cyan));
-       stroke: var(--cyan)!important;
-     }
-   `}} />
  </defs>

Add these rules to landing/src/index.css:

/* Architecture Diagram Animations */
.anim-path {
  stroke: var(--border2);
  stroke-width: 1.5;
  stroke-dasharray: 6 4;
  animation: dash 30s linear infinite;
}

.anim-path-active {
  stroke: var(--cyan);
  stroke-width: 2;
  filter: drop-shadow(0 0 2px var(--cyan));
}

`@keyframes` dash {
  to {
    stroke-dashoffset: -1000;
  }
}

.arch-node {
  transition: all 0.3s;
}

.arch-node:hover {
  filter: drop-shadow(0 0 4px var(--cyan));
  stroke: var(--cyan) !important;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/ArchitectureDiagram.tsx` around lines 56 - 80, The
inline CSS injected via the <style dangerouslySetInnerHTML> block in the
ArchitectureDiagram component should be moved into the global stylesheet: copy
the rules for .anim-path, .anim-path-active, `@keyframes` dash, .arch-node and
.arch-node:hover from the style block in ArchitectureDiagram.tsx into
landing/src/index.css, remove the <style> element from the SVG <defs> in the
ArchitectureDiagram component, and ensure the index.css is imported/loaded so
those classes and keyframes still apply to the SVG elements that reference
.anim-path, .anim-path-active and .arch-node.

Source: Linters/SAST tools


113-117: 💤 Low value

Consider extracting SVG paths to named constants.

The animation path strings (lines 115-116) are very long and reduce readability. Extract them to named constants at the top of the component to clarify intent and improve maintainability.

♻️ Optional refactor
+const ALLOW_PATH = "M 150 180 H 210 M 210 180 H 360 M 360 180 H 410 M 410 180 H 540 M 540 180 H 600 M 600 180 H 680 M 680 180 H 710";
+const BLOCK_PATH = "M 150 180 H 210 M 210 180 H 360 M 360 180 H 380 V 100 H 410 M 410 100 H 540 M 540 100 H 570 V 180 H 600 M 600 180 H 680 M 680 180 H 710";
+
 export default function ArchitectureDiagram() {
   // ... state ...

   // ... in the JSX ...
   <animateMotion
     dur="4s"
     repeatCount="indefinite"
-    path={
-      cycle === 2
-        ? "M 150 180 H 210 M 210 180 H 360 M 360 180 H 380 V 100 H 410 M 410 100 H 540 M 540 100 H 570 V 180 H 600 M 600 180 H 680 M 680 180 H 710" // block path triggers heuristic block
-        : "M 150 180 H 210 M 210 180 H 360 M 360 180 H 410 M 410 180 H 540 M 540 180 H 600 M 600 180 H 680 M 680 180 H 710" // allow path
-    }
+    path={cycle === 2 ? BLOCK_PATH : ALLOW_PATH}
   />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/ArchitectureDiagram.tsx` around lines 113 - 117, The
long inline SVG path strings in the ArchitectureDiagram component reduce
readability; extract them into named constants (e.g., BLOCK_PATH and ALLOW_PATH)
declared at the top of the component (or module scope) and replace the inline
strings used in the path prop (the ternary that checks cycle === 2) with those
constants; ensure the constants have clear names and preserve the exact string
values so the animation behavior of the path prop and any references to cycle
remain unchanged.
landing/src/components/Footer.tsx (2)

76-76: ⚡ Quick win

Redundant inline styles on screen-reader element.

The element already has className="sr-only", so the inline style object duplicates styles that should be handled by the .sr-only CSS class. Remove the inline styles.

♻️ Proposed fix
-      <div className="sr-only" aria-live="polite" style={{ position: 'absolute', width: '1px', height: '1px', padding: 0, margin: '-1px', overflow: 'hidden', clip: 'rect(0, 0, 0, 0)', border: 0 }}>
+      <div className="sr-only" aria-live="polite">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/Footer.tsx` at line 76, The div in the Footer
component using className="sr-only" redundantly includes an inline style object;
remove the style prop from that element so the .sr-only CSS class controls the
hidden visuals, keeping aria-live="polite" and position semantics intact (locate
the div in Footer.tsx that has className="sr-only" and remove the style={{ ...
}} attribute).

8-19: ⚖️ Poor tradeoff

Consider throttling the scroll event handler.

Scroll events fire at high frequency. While the current logic is simple enough that performance impact is likely minimal, throttling the handler (e.g., to ~100ms) would further reduce unnecessary re-renders and improve performance on lower-end devices.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/Footer.tsx` around lines 8 - 19, The scroll handler in
the useEffect (handleScroll) runs on every scroll event which can cause
excessive calls to setShowBackToTop; wrap or replace handleScroll with a
throttled version (e.g., use a ref timer or lodash.throttle) so it only runs
~every 100ms, attach the throttled function in window.addEventListener and
ensure you cancel/clean up the throttle on unmount (clear the timer or call
cancel on lodash.throttle) to avoid leaks; update references to handleScroll,
setShowBackToTop, and the useEffect cleanup accordingly.
landing/src/components/ComparisonTable.tsx (2)

43-43: ⚡ Quick win

Inconsistent SVG viewBox dimensions.

The star icon has width="10" height="10" but viewBox="0 0 24 24". While this will auto-scale, it creates confusion and inconsistency compared to the other icons which use matching dimensions (e.g., line 24: width="12" height="12" viewBox="0 0 12 12").

♻️ Proposed fix for consistency
-         <svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
-           <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26" />
+         <svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor" stroke="currentColor" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round">
+           <polygon points="5 0.8 6.3 3.4 9.2 3.9 7.1 5.9 7.6 8.8 5 7.4 2.4 8.8 2.9 5.9 0.8 3.9 3.7 3.4" />
         </svg>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/ComparisonTable.tsx` at line 43, The SVG for the star
icon in ComparisonTable (the inline <svg> element) uses width="10" height="10"
but viewBox="0 0 24 24", causing inconsistent scaling with other icons; update
either the viewBox to "0 0 10 10" (or "0 0 12 12" to match others) or change
width/height to "24" so the viewBox and rendered size match—locate the star
<svg> element in ComparisonTable.tsx and make the dimensions consistent with the
other icons.

84-110: ⚡ Quick win

Extract duplicated progress bar markup.

The progress bar rendering logic is duplicated three times (lines 87-92, 95-100, 103-108) with only the rating level, color, and label varying. Extract this into a helper function to follow the DRY principle.

♻️ Proposed refactor
+ const renderProgressBar = (filledCount: number, color: string, label: string) => (
+   <div className="progress-bar" title={`${filledCount} out of 3 rating`} style={{ justifyContent: 'center' }}>
+     {[1, 2, 3].map(i => (
+       <div
+         key={i}
+         className={`progress-pill${i <= filledCount ? ' filled' : ''}`}
+         style={i <= filledCount ? { color } : {}}
+       />
+     ))}
+     <span style={{ marginLeft: '6px', fontSize: '11px', fontFamily: 'var(--mono)', color: filledCount === 3 ? color : 'var(--text3)' }}>
+       {label}
+     </span>
+   </div>
+ );

  <tr className="compare-summary-row">
    <td>Overall Protection Rating</td>
-   <td className="text-center">
-     <div className="progress-bar" title="1 out of 3 rating" style={{ justifyContent: 'center' }}>
-       <div className="progress-pill filled" style={{ color: 'var(--red)' }} />
-       <div className="progress-pill" />
-       <div className="progress-pill" />
-       <span style={{ marginLeft: '6px', fontSize: '11px', fontFamily: 'var(--mono)', color: 'var(--text3)' }}>Weak</span>
-     </div>
-   </td>
+   <td className="text-center">{renderProgressBar(1, 'var(--red)', 'Weak')}</td>
-   <td className="text-center">
-     <div className="progress-bar" title="2 out of 3 rating" style={{ justifyContent: 'center' }}>
-       <div className="progress-pill filled" style={{ color: 'var(--amber)' }} />
-       <div className="progress-pill filled" style={{ color: 'var(--amber)' }} />
-       <div className="progress-pill" />
-       <span style={{ marginLeft: '6px', fontSize: '11px', fontFamily: 'var(--mono)', color: 'var(--text3)' }}>Medium</span>
-     </div>
-   </td>
+   <td className="text-center">{renderProgressBar(2, 'var(--amber)', 'Medium')}</td>
-   <td className="text-center" style={{ borderLeft: '2px solid rgba(0,229,255,0.15)' }}>
-     <div className="progress-bar" title="3 out of 3 rating" style={{ justifyContent: 'center' }}>
-       <div className="progress-pill filled" style={{ color: 'var(--green)' }} />
-       <div className="progress-pill filled" style={{ color: 'var(--green)' }} />
-       <div className="progress-pill filled" style={{ color: 'var(--green)' }} />
-       <span style={{ marginLeft: '6px', fontSize: '11px', fontFamily: 'var(--mono)', color: 'var(--green)' }}>Maximum</span>
-     </div>
-   </td>
+   <td className="text-center" style={{ borderLeft: '2px solid rgba(0,229,255,0.15)' }}>
+     {renderProgressBar(3, 'var(--green)', 'Maximum')}
+   </td>
  </tr>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/ComparisonTable.tsx` around lines 84 - 110, The
progress bar markup in ComparisonTable is duplicated; create a helper React
function (e.g., renderProgressBar or ProgressPill) inside the ComparisonTable
component that accepts props for filledCount (or rating), color, label and title
and returns the single progress-bar JSX (progress-bar div, three progress-pill
divs with the correct filled state/styles and the label span). Replace the three
inline blocks in the table row with calls to this helper (pass
1/'var(--red)'/'Weak', 2/'var(--amber)'/'Medium', 3/'var(--green)'/'Maximum')
and preserve the special borderLeft style on the third cell by passing an
optional cellStyle prop or wrapping TD accordingly.
landing/src/components/FeaturesSection.tsx (3)

8-8: ⚡ Quick win

Clarify abbreviated property names.

The properties l and c in the tag objects are cryptic. Use descriptive names label and color to improve code readability and maintainability.

♻️ Proposed refactor
-  tags: { l: string; c: string }[];
+  tags: { label: string; color: string }[];

Then update all usages in the FEATURES array and the render logic accordingly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/FeaturesSection.tsx` at line 8, Replace the cryptic
tag properties `l` and `c` with descriptive names `label` and `color` across the
component: update the Tags type declaration in FeaturesSection (change tags: {
l: string; c: string }[] to tags: { label: string; color: string }[]), update
every entry in the FEATURES array to use label and color instead of l and c, and
update all render logic/places that reference tag.l or tag.c (including any
destructuring and JSX where tags are mapped) to use tag.label and tag.color so
types and usages remain consistent.

134-167: ⚡ Quick win

Consider object lookup for color mapping.

The switch statement can be replaced with an object lookup for more concise and maintainable code.

♻️ Proposed refactor
-  const tagCss = (c: string) => {
-    switch (c) {
-      case 'cyan':
-        return {
-          color: 'var(--cyan)',
-          background: 'var(--cyan-dim)',
-          borderColor: 'rgba(0, 229, 255, 0.2)'
-        };
-      case 'purple':
-        return {
-          color: 'var(--purple)',
-          background: 'var(--purple-dim)',
-          borderColor: 'rgba(180, 127, 255, 0.2)'
-        };
-      case 'amber':
-        return {
-          color: 'var(--amber)',
-          background: 'var(--amber-dim)',
-          borderColor: 'rgba(255, 184, 0, 0.2)'
-        };
-      case 'green':
-        return {
-          color: 'var(--green)',
-          background: 'var(--green-dim)',
-          borderColor: 'rgba(0, 255, 136, 0.2)'
-        };
-      default:
-        return {
-          color: 'var(--text3)',
-          background: 'var(--surface3)',
-          borderColor: 'var(--border)'
-        };
-    }
-  };
+  const TAG_STYLES: Record<string, React.CSSProperties> = {
+    cyan: {
+      color: 'var(--cyan)',
+      background: 'var(--cyan-dim)',
+      borderColor: 'rgba(0, 229, 255, 0.2)'
+    },
+    purple: {
+      color: 'var(--purple)',
+      background: 'var(--purple-dim)',
+      borderColor: 'rgba(180, 127, 255, 0.2)'
+    },
+    amber: {
+      color: 'var(--amber)',
+      background: 'var(--amber-dim)',
+      borderColor: 'rgba(255, 184, 0, 0.2)'
+    },
+    green: {
+      color: 'var(--green)',
+      background: 'var(--green-dim)',
+      borderColor: 'rgba(0, 255, 136, 0.2)'
+    }
+  };
+
+  const tagCss = (c: string) => TAG_STYLES[c] ?? {
+    color: 'var(--text3)',
+    background: 'var(--surface3)',
+    borderColor: 'var(--border)'
+  };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/FeaturesSection.tsx` around lines 134 - 167, The
tagCss switch in the tagCss(c: string) function is verbose; replace it with a
lookup object (e.g., const TAG_STYLES = { cyan: { ... }, purple: { ... }, amber:
{ ... }, green: { ... } }) and return TAG_STYLES[c] ?? defaultStyle to make the
mapping concise and maintainable; ensure the lookup and default have the same
return shape (match the existing object structure or CSS style type) and update
any type annotations if necessary so tagCss still returns the expected style
object.

105-117: 💤 Low value

Redundant observer disconnect in cleanup.

The observer is already disconnected when the element becomes visible (line 109), making the cleanup disconnect (line 116) redundant. While harmless, you can optimize by conditionally disconnecting only if still observing.

♻️ Optional optimization
  useEffect(() => {
+   let disconnected = false;
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) {
        setVisible(true);
        observer.disconnect();
+       disconnected = true;
      }
    }, { threshold: 0.05 });

    if (ref.current) {
      observer.observe(ref.current);
    }
-   return () => observer.disconnect();
+   return () => {
+     if (!disconnected) observer.disconnect();
+   };
  }, []);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/FeaturesSection.tsx` around lines 105 - 117, The
IntersectionObserver is always disconnected inside the callback when the element
becomes visible, so the cleanup disconnect is redundant; update the useEffect
that creates the observer to track whether we already disconnected (e.g.,
declare a local boolean like didDisconnect = false, set didDisconnect = true
when you call observer.disconnect() inside the ([entry]) callback after
setVisible(true)), and in the cleanup only call observer.disconnect() if
didDisconnect is false; reference the useEffect that creates the
IntersectionObserver, the observer variable, ref.current, setVisible, and the
callback that checks entry.isIntersecting.
landing/src/components/BreachCards.tsx (1)

186-201: 💤 Low value

Consider adding explicit type="button" attribute.

While the button correctly defaults to button behavior outside of a form context, explicitly adding type="button" is a defensive best practice that prevents unexpected submit behavior if the component is later used within a form.

🔧 Optional improvement
 <button
+  type="button"
   className="threat-resp-toggle"
   onClick={() => toggleExpand(t.id)}
   aria-expanded={isExpanded}
   aria-controls={`threat-resp-${t.id}`}
 >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/BreachCards.tsx` around lines 186 - 201, The button in
BreachCards.tsx (the element that calls toggleExpand(t.id) and uses isExpanded /
aria-controls={`threat-resp-${t.id}`}) should include an explicit type="button"
attribute to prevent it from acting as a form submit button if this component is
ever rendered inside a form; update that <button> to add type="button" while
keeping the existing onClick, aria-expanded and aria-controls props unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@landing/index.html`:
- Line 10: The meta tag with name="keywords" in the landing page contains a
typo: replace the term "claud security" inside the content attribute of the meta
tag (the comma-separated keywords string) with "claude security" so the SEO
keywords read correctly.

In `@landing/src/components/BreachCards.tsx`:
- Around line 77-107: The FadeUp component is duplicated; extract it into a
single shared component file (e.g., landing/src/components/FadeUp.tsx) and
update usages in BreachCards (FadeUp function) and InstallSection (its
duplicate) to import and use the new exported FadeUp component; ensure the new
file exports the same signature (props: children, delay?: number) and preserves
useRef, useState, useEffect, IntersectionObserver logic and transition styles so
both BreachCards and InstallSection import FadeUp instead of declaring their own
local FadeUp functions.

In `@landing/src/components/DemoSection.tsx`:
- Line 72: In DemoSection (component DemoSection.tsx) fix the typo in the bullet
string 'Request requests safety guidelines bypass.' by replacing it with a clear
phrase such as 'Request for safety guidelines bypass' or 'Requests safety
guidelines bypass' (choose one) so the bullet reads correctly; locate the string
in the component render/array of bullets and update that literal accordingly.
- Around line 117-122: The setTimeout in handleCopy can update state after
unmount; track and clear it by storing the timeout ID in a ref (e.g.,
copyTimeoutRef) and on unmount clearTimeout for any stored IDs in a useEffect
cleanup. Update handleCopy to assign the timeout ID to copyTimeoutRef.current
instead of calling setTimeout directly, and ensure useEffect returns a cleanup
that clears copyTimeoutRef.current and resets it to null to prevent setCopiedId
from running after unmount.
- Around line 117-122: The handleCopy function currently calls
navigator.clipboard.writeText(attack.prompt) without handling failures; update
handleCopy to await the clipboard write inside a try/catch (or handle the
returned Promise) and only call setCopiedId(attack.id) on success, while on
error log/report the error and provide fallback UX (e.g., show an error toast or
disable "Copied!" state) and consider a fallback copy method for older browsers;
references: handleCopy, navigator.clipboard.writeText, setCopiedId.

In `@landing/src/components/FeaturesSection.tsx`:
- Line 183: Feature icons are hardcoded to var(--cyan) in the feat-icon div
inside the FeaturesSection render, causing color mismatch with themed features;
update the FEATURES data (or FeatureItem type) to include a color property
(e.g., 'var(--purple)' / 'var(--amber)' / 'var(--green)' / 'var(--cyan)') and
then change the inline style on the div with className "feat-icon" to use that
feature.color instead of the hardcoded 'var(--cyan)' so each FeatureItem renders
its matching icon color.

In `@landing/src/components/Footer.tsx`:
- Line 38: The Footer component still uses inline style objects (notably the
outer container div and the other elements around it) which violates the
consolidation goal; remove these inline style props in
landing/src/components/Footer.tsx and add corresponding CSS classes (e.g.,
.footer-inner, .footer-links, .footer-right) in landing/src/index.css capturing
display:flex, justify-content, align-items, flex-wrap, gap and any other layout
rules, then replace the inline style attributes with className="footer-inner"
(and className="footer-links" / "footer-right" for the other elements
referenced) so all layout styling is centralized in index.css.

In `@landing/src/components/InstallSection.tsx`:
- Around line 139-150: The install command element (rendered with className
"install-cmd") is clickable but not keyboard accessible; change the div to a
semantic, focusable control (replace the div with a <button> element) so
Enter/Space activate it and keep the existing onClick={() => handleCopy(d.cmd)}
and title intact, ensure the JSX still references copiedCmd === d.cmd for the
label, and update the .install-cmd CSS to neutralize default button styles
(background: none; border: none; width: 100%; text-align: left; cursor:
pointer;) so visual layout and behavior remain the same.
- Line 1: The FadeUp animated component is duplicated across multiple files
(InstallSection.tsx, CtaSection.tsx, BreachCards.tsx, PipelineSection.tsx);
extract it into a single shared component (e.g.,
landing/src/components/FadeUp.tsx) that exports FadeUp({children, delay?}), copy
the existing implementation (useRef, useState, useEffect with
IntersectionObserver and transition styles) into that file, then replace the
local FadeUp definitions in InstallSection, CtaSection, BreachCards, and
PipelineSection with an import of the shared FadeUp and use it unchanged so all
animation logic is centralized.
- Around line 108-112: The handleCopy function currently calls
navigator.clipboard.writeText without checking availability or handling
rejections; update handleCopy to first check that navigator.clipboard and
navigator.clipboard.writeText exist, then call writeText inside a try/catch (or
use .catch) to handle promise rejections, and on success call setCopiedCmd(cmd)
and the timeout, while on failure use a graceful fallback (e.g., select/copy via
a temporary textarea or set an error state/notification) so the UI provides
feedback when copying is not supported or fails. Reference handleCopy,
navigator.clipboard.writeText, and setCopiedCmd when making the changes.
- Around line 73-103: The FadeUp component is duplicated across multiple
components; extract the existing FadeUp implementation into a single shared
component (exported as FadeUp) preserving its props (children, delay?) and
behavior (useRef, useState, useEffect with IntersectionObserver and the same
style/transition), then replace the inline copies in InstallSection, CtaSection,
BreachCards, and PipelineSection with imports of that shared FadeUp and use it
instead of the local definitions so any future animation change is made in one
place.

In `@landing/src/components/PipelineSection.tsx`:
- Around line 46-72: The inline <style> in the PipelineSection component injects
static `@keyframes` and .pipeline-packet-active via dangerouslySetInnerHTML; move
the two keyframe rules packetTravelAllow and packetTravelBlock and the
.pipeline-packet-active class into the global stylesheet (index.css) alongside
other animations, then remove the <style dangerouslySetInnerHTML={...}> block
from PipelineSection.tsx so the component uses the external CSS
classes/animations instead of inlined rules.

In `@landing/src/components/StatsSection.tsx`:
- Around line 79-123: The last item in the stats array inside StatsSection uses
a hardcoded color '`#FFB800`' for the "LLM providers" stat; replace that literal
with a CSS custom property to match the others (e.g. set color: 'var(--amber)'
or a new variable like 'var(--gold)' if you prefer) so the label "LLM providers"
in the stats array uses a theme variable instead of a hex literal and remains
consistent with the other entries.
- Around line 47-77: The FadeUp component is duplicated; extract it into a
single shared component and import it where needed: create a new exported
component FadeUp (the same logic using useRef/useState/useEffect and
IntersectionObserver) and remove the inline definitions in both StatsSection and
PipelineSection; then replace the local FadeUp usages in StatsSection (function
FadeUp) and PipelineSection with imports of the shared FadeUp component and
ensure the prop signature ({ children, delay?: number }) is preserved.

In `@landing/src/components/TenetLogo.tsx`:
- Around line 6-15: The TenetLogo component uses hardcoded SVG gradient IDs
("tg1", "tg2") which collide when multiple instances render; update TenetLogo to
generate unique IDs per instance (e.g., use React's useId()) and replace the
hardcoded ids in the <linearGradient id="..."> definitions and all gradient
references (e.g., fill="url(`#tg1`)" / fill="url(`#tg2`)") with the generated ids
(e.g., `grad1`, `grad2`) so each instance produces unique `url(#${grad1})` and
`url(#${grad2})` references.
- Line 3: The TenetLogo component's size prop is untyped—add a TypeScript
annotation to restore type safety: update the TenetLogo declaration to accept a
typed prop (e.g., size: number) or define an interface (e.g., TenetLogoProps {
size?: number }) and annotate the function parameter and return type (e.g.,
TenetLogo(props: TenetLogoProps): JSX.Element). Ensure default value remains
(size = 32) and update any exports/signatures accordingly so editors and the
type checker recognize the prop type.

In `@landing/src/components/TerminalPanel.tsx`:
- Around line 32-67: The effect recreates the setInterval because idx is in the
dependency array while setIdx is updated inside the interval; remove idx from
the useEffect dependency array and instead track the counter with a ref (e.g.,
create idxRef and update idxRef.current inside the interval) or use only
functional updates for state (call setIdx(i => i + 1) inside the interval) so
the effect can run once; ensure the interval cleanup (clearInterval) remains and
any UI state that needs to reflect idx is updated from state (setIdx) or
synchronized from idxRef when necessary (refer to useEffect, setInterval,
setIdx, idxRef, setLines, setBars, setFlashRed).

In `@landing/src/index.css`:
- Line 205: The CSS rule for .demo-prompt-text contains two font-size
declarations (13px and 11.5px); remove the duplicate/unused declaration so only
the intended font-size remains — e.g., delete the first font-size:13px or
consolidate to a single font-size:11.5px in the .demo-prompt-text rule to
eliminate the dead code.

---

Nitpick comments:
In @.gitignore:
- Around line 207-210: The .gitignore currently excludes package-lock.json which
prevents committing the npm lockfile and breaks reproducible installs; edit
.gitignore to stop ignoring package-lock.json (remove the "package-lock.json"
entry) so the project's npm lockfile is versioned alongside sources and ensures
deterministic dependency resolution.

In `@landing/src/App.tsx`:
- Line 27: The main element with id "main-content" in App.tsx isn't reliably
focusable after using the skip link; update the JSX for the <main
id="main-content" className="main"> element to include tabIndex={-1} so keyboard
focus can be programmatically moved to it (ensure the attribute is added to the
main element in the App component).

In `@landing/src/components/ArchitectureDiagram.tsx`:
- Around line 56-80: The inline CSS injected via the <style
dangerouslySetInnerHTML> block in the ArchitectureDiagram component should be
moved into the global stylesheet: copy the rules for .anim-path,
.anim-path-active, `@keyframes` dash, .arch-node and .arch-node:hover from the
style block in ArchitectureDiagram.tsx into landing/src/index.css, remove the
<style> element from the SVG <defs> in the ArchitectureDiagram component, and
ensure the index.css is imported/loaded so those classes and keyframes still
apply to the SVG elements that reference .anim-path, .anim-path-active and
.arch-node.
- Around line 113-117: The long inline SVG path strings in the
ArchitectureDiagram component reduce readability; extract them into named
constants (e.g., BLOCK_PATH and ALLOW_PATH) declared at the top of the component
(or module scope) and replace the inline strings used in the path prop (the
ternary that checks cycle === 2) with those constants; ensure the constants have
clear names and preserve the exact string values so the animation behavior of
the path prop and any references to cycle remain unchanged.

In `@landing/src/components/BreachCards.tsx`:
- Around line 186-201: The button in BreachCards.tsx (the element that calls
toggleExpand(t.id) and uses isExpanded / aria-controls={`threat-resp-${t.id}`})
should include an explicit type="button" attribute to prevent it from acting as
a form submit button if this component is ever rendered inside a form; update
that <button> to add type="button" while keeping the existing onClick,
aria-expanded and aria-controls props unchanged.

In `@landing/src/components/ComparisonTable.tsx`:
- Line 43: The SVG for the star icon in ComparisonTable (the inline <svg>
element) uses width="10" height="10" but viewBox="0 0 24 24", causing
inconsistent scaling with other icons; update either the viewBox to "0 0 10 10"
(or "0 0 12 12" to match others) or change width/height to "24" so the viewBox
and rendered size match—locate the star <svg> element in ComparisonTable.tsx and
make the dimensions consistent with the other icons.
- Around line 84-110: The progress bar markup in ComparisonTable is duplicated;
create a helper React function (e.g., renderProgressBar or ProgressPill) inside
the ComparisonTable component that accepts props for filledCount (or rating),
color, label and title and returns the single progress-bar JSX (progress-bar
div, three progress-pill divs with the correct filled state/styles and the label
span). Replace the three inline blocks in the table row with calls to this
helper (pass 1/'var(--red)'/'Weak', 2/'var(--amber)'/'Medium',
3/'var(--green)'/'Maximum') and preserve the special borderLeft style on the
third cell by passing an optional cellStyle prop or wrapping TD accordingly.

In `@landing/src/components/DemoSection.tsx`:
- Line 192: Rename the CSS class used for the typing cursor to a more
component-agnostic name and update the stylesheet reference: change the span in
DemoSection.tsx where status === 'typing' currently uses className "hero-cursor"
to a generic name like "typing-cursor" (or "blink-cursor") and then update the
corresponding rule in landing/src/index.css from .hero-cursor to the new class
name so the component is decoupled from Hero-specific styling.

In `@landing/src/components/FeaturesSection.tsx`:
- Line 8: Replace the cryptic tag properties `l` and `c` with descriptive names
`label` and `color` across the component: update the Tags type declaration in
FeaturesSection (change tags: { l: string; c: string }[] to tags: { label:
string; color: string }[]), update every entry in the FEATURES array to use
label and color instead of l and c, and update all render logic/places that
reference tag.l or tag.c (including any destructuring and JSX where tags are
mapped) to use tag.label and tag.color so types and usages remain consistent.
- Around line 134-167: The tagCss switch in the tagCss(c: string) function is
verbose; replace it with a lookup object (e.g., const TAG_STYLES = { cyan: { ...
}, purple: { ... }, amber: { ... }, green: { ... } }) and return TAG_STYLES[c]
?? defaultStyle to make the mapping concise and maintainable; ensure the lookup
and default have the same return shape (match the existing object structure or
CSS style type) and update any type annotations if necessary so tagCss still
returns the expected style object.
- Around line 105-117: The IntersectionObserver is always disconnected inside
the callback when the element becomes visible, so the cleanup disconnect is
redundant; update the useEffect that creates the observer to track whether we
already disconnected (e.g., declare a local boolean like didDisconnect = false,
set didDisconnect = true when you call observer.disconnect() inside the
([entry]) callback after setVisible(true)), and in the cleanup only call
observer.disconnect() if didDisconnect is false; reference the useEffect that
creates the IntersectionObserver, the observer variable, ref.current,
setVisible, and the callback that checks entry.isIntersecting.

In `@landing/src/components/Footer.tsx`:
- Line 76: The div in the Footer component using className="sr-only" redundantly
includes an inline style object; remove the style prop from that element so the
.sr-only CSS class controls the hidden visuals, keeping aria-live="polite" and
position semantics intact (locate the div in Footer.tsx that has
className="sr-only" and remove the style={{ ... }} attribute).
- Around line 8-19: The scroll handler in the useEffect (handleScroll) runs on
every scroll event which can cause excessive calls to setShowBackToTop; wrap or
replace handleScroll with a throttled version (e.g., use a ref timer or
lodash.throttle) so it only runs ~every 100ms, attach the throttled function in
window.addEventListener and ensure you cancel/clean up the throttle on unmount
(clear the timer or call cancel on lodash.throttle) to avoid leaks; update
references to handleScroll, setShowBackToTop, and the useEffect cleanup
accordingly.

In `@landing/src/components/Hero.tsx`:
- Line 74: The SVG in Hero.tsx uses an inline style (style={{ marginRight: '6px'
}}) which should be moved to the global stylesheet; add the rule
.hero-pill-check { margin-right: 6px; } (or adjust the existing .hero-pill-check
selector in landing/src/index.css) and remove the style prop from the SVG
element in the Hero component so styling is consolidated into the CSS file.

In `@landing/src/components/Navbar.tsx`:
- Around line 203-207: Navbar currently indicates the active section only
visually; update the anchor elements that render navigation items (the desktop
link loop around the JSX using key={item.id}, href={`#${item.id}`} and className
based on activeSection) and the corresponding mobile menu anchors (the similar
block around lines 290-294) to include aria-current="page" when activeSection
=== item.id; i.e., conditionally add aria-current="page" for the active link in
both desktop and mobile render paths so screen readers receive the active state
(no other behavior changes required—leave handleLinkClick, activeSection logic
intact).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bd77cfed-3c30-481b-9e12-1f9bc0e288d2

📥 Commits

Reviewing files that changed from the base of the PR and between 721686f and 0e39b94.

📒 Files selected for processing (19)
  • .gitignore
  • landing/index.html
  • landing/src/App.tsx
  • landing/src/components/ArchitectureDiagram.tsx
  • landing/src/components/BreachCards.tsx
  • landing/src/components/ComparisonTable.tsx
  • landing/src/components/CtaSection.tsx
  • landing/src/components/DemoSection.tsx
  • landing/src/components/FeaturesSection.tsx
  • landing/src/components/Footer.tsx
  • landing/src/components/Hero.tsx
  • landing/src/components/InstallSection.tsx
  • landing/src/components/Navbar.tsx
  • landing/src/components/PipelineSection.tsx
  • landing/src/components/StatsSection.tsx
  • landing/src/components/TenetLogo.tsx
  • landing/src/components/TerminalPanel.tsx
  • landing/src/index.css
  • landing/src/main.tsx

Comment thread landing/index.html Outdated
Comment on lines +77 to +107
function FadeUp({ children, delay = 0 }: { children: React.ReactNode; delay?: number }) {
const ref = useRef<HTMLDivElement>(null);
const [visible, setVisible] = useState(false);

useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
setVisible(true);
observer.disconnect();
}
}, { threshold: 0.05 });

if (ref.current) {
observer.observe(ref.current);
}
return () => observer.disconnect();
}, []);

return (
<div
ref={ref}
style={{
opacity: visible ? 1 : 0,
transform: visible ? 'none' : 'translateY(16px)',
transition: `opacity 0.5s ${delay}s ease-out, transform 0.5s ${delay}s ease-out`
}}
>
{children}
</div>
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Extract duplicated FadeUp component to shared utility.

The FadeUp component is duplicated in both InstallSection.tsx and this file with identical implementations. Extract it to a shared component file (e.g., landing/src/components/FadeUp.tsx or landing/src/utils/FadeUp.tsx) to maintain a single source of truth.

♻️ Suggested refactor

Create landing/src/components/FadeUp.tsx:

import React, { useState, useEffect, useRef } from 'react';

export default function FadeUp({ children, delay = 0 }: { children: React.ReactNode; delay?: number }) {
  const ref = useRef<HTMLDivElement>(null);
  const [visible, setVisible] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) {
        setVisible(true);
        observer.disconnect();
      }
    }, { threshold: 0.05 });

    if (ref.current) {
      observer.observe(ref.current);
    }
    return () => observer.disconnect();
  }, []);

  return (
    <div
      ref={ref}
      style={{
        opacity: visible ? 1 : 0,
        transform: visible ? 'none' : 'translateY(16px)',
        transition: `opacity 0.5s ${delay}s ease-out, transform 0.5s ${delay}s ease-out`
      }}
    >
      {children}
    </div>
  );
}

Then update both BreachCards.tsx and InstallSection.tsx:

-import React, { useState, useEffect, useRef } from 'react';
+import React, { useState, useEffect, useRef } from 'react';
+import FadeUp from './FadeUp';

-function FadeUp({ children, delay = 0 }: { children: React.ReactNode; delay?: number }) {
-  const ref = useRef<HTMLDivElement>(null);
-  const [visible, setVisible] = useState(false);
-
-  useEffect(() => {
-    const observer = new IntersectionObserver(([entry]) => {
-      if (entry.isIntersecting) {
-        setVisible(true);
-        observer.disconnect();
-      }
-    }, { threshold: 0.05 });
-
-    if (ref.current) {
-      observer.observe(ref.current);
-    }
-    return () => observer.disconnect();
-  }, []);
-
-  return (
-    <div
-      ref={ref}
-      style={{
-        opacity: visible ? 1 : 0,
-        transform: visible ? 'none' : 'translateY(16px)',
-        transition: `opacity 0.5s ${delay}s ease-out, transform 0.5s ${delay}s ease-out`
-      }}
-    >
-      {children}
-    </div>
-  );
-}
-
 export default function BreachCards() {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/BreachCards.tsx` around lines 77 - 107, The FadeUp
component is duplicated; extract it into a single shared component file (e.g.,
landing/src/components/FadeUp.tsx) and update usages in BreachCards (FadeUp
function) and InstallSection (its duplicate) to import and use the new exported
FadeUp component; ensure the new file exports the same signature (props:
children, delay?: number) and preserves useRef, useState, useEffect,
IntersectionObserver logic and transition styles so both BreachCards and
InstallSection import FadeUp instead of declaring their own local FadeUp
functions.

Comment thread landing/src/components/DemoSection.tsx Outdated
Comment thread landing/src/components/DemoSection.tsx Outdated
{FEATURES.map((f, i) => (
<FadeUp key={i} delay={i * 0.07}>
<div className="feat-card">
<div className="feat-icon" style={{ background: f.bg, color: 'var(--cyan)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Icon color mismatch with feature theming.

All feature icons are hardcoded to var(--cyan), but features use different background colors (var(--cyan-dim), var(--purple-dim), var(--amber-dim), var(--green-dim)). This creates a visual inconsistency where purple/amber/green-themed features all display cyan icons.

🎨 Proposed fix to match icon color with background theme

Add a color property to each FeatureItem and use it:

  interface FeatureItem {
    icon: React.ReactNode;
    bg: string;
+   color: string;
    title: string;
    desc: string;
    tags: { l: string; c: string }[];
  }

Then update FEATURES to specify the matching color for each feature:

  {
    icon: ...,
    bg: 'var(--cyan-dim)',
+   color: 'var(--cyan)',
    title: 'Heuristic Detection',
    ...
  },
  {
    icon: ...,
    bg: 'var(--purple-dim)',
+   color: 'var(--purple)',
    title: 'ML-Based Analysis',
    ...
  },
  // etc.

And use it in the render:

- <div className="feat-icon" style={{ background: f.bg, color: 'var(--cyan)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
+ <div className="feat-icon" style={{ background: f.bg, color: f.color, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/FeaturesSection.tsx` at line 183, Feature icons are
hardcoded to var(--cyan) in the feat-icon div inside the FeaturesSection render,
causing color mismatch with themed features; update the FEATURES data (or
FeatureItem type) to include a color property (e.g., 'var(--purple)' /
'var(--amber)' / 'var(--green)' / 'var(--cyan)') and then change the inline
style on the div with className "feat-icon" to use that feature.color instead of
the hardcoded 'var(--cyan)' so each FeatureItem renders its matching icon color.

Comment thread landing/src/components/StatsSection.tsx
@@ -0,0 +1,45 @@
import React from 'react';

export default function TenetLogo({ size = 32 }) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add TypeScript type annotation for the size prop.

The project uses TypeScript (evident from React.MouseEvent<HTMLAnchorElement> usage in Footer.tsx), but the size parameter lacks a type annotation. This reduces type safety and IDE support.

♻️ Proposed fix
-export default function TenetLogo({ size = 32 }) {
+export default function TenetLogo({ size = 32 }: { size?: number }) {

Or define a props interface:

+interface TenetLogoProps {
+  size?: number;
+}
+
-export default function TenetLogo({ size = 32 }) {
+export default function TenetLogo({ size = 32 }: TenetLogoProps) {
📝 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 default function TenetLogo({ size = 32 }) {
export default function TenetLogo({ size = 32 }: { size?: number }) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/TenetLogo.tsx` at line 3, The TenetLogo component's
size prop is untyped—add a TypeScript annotation to restore type safety: update
the TenetLogo declaration to accept a typed prop (e.g., size: number) or define
an interface (e.g., TenetLogoProps { size?: number }) and annotate the function
parameter and return type (e.g., TenetLogo(props: TenetLogoProps): JSX.Element).
Ensure default value remains (size = 32) and update any exports/signatures
accordingly so editors and the type checker recognize the prop type.

Comment on lines +6 to +15
<defs>
<linearGradient id="tg1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#00e5ff" />
<stop offset="100%" stopColor="#0099cc" />
</linearGradient>
<linearGradient id="tg2" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#00aadd" />
<stop offset="100%" stopColor="#006699" />
</linearGradient>
</defs>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

SVG gradient ID collision when multiple instances are rendered.

The gradient IDs tg1 and tg2 are hardcoded and will collide when multiple TenetLogo components are rendered on the same page. Evidence from the codebase shows the logo is used in at least Navbar (size 28) and Footer (size 24), and likely Hero. This causes undefined rendering behavior—browsers may display incorrect gradients or fail to render them.

🐛 Proposed fix using unique IDs

Generate unique gradient IDs per instance using useId() (React 18+):

-import React from 'react';
+import React, { useId } from 'react';

-export default function TenetLogo({ size = 32 }) {
+export default function TenetLogo({ size = 32 }: { size?: number }) {
+  const id = useId();
+  const grad1 = `tg1-${id}`;
+  const grad2 = `tg2-${id}`;
+
   return (
     <svg width={size} height={size} viewBox="0 0 80 80" fill="none">
       <defs>
-        <linearGradient id="tg1" x1="0%" y1="0%" x2="100%" y2="100%">
+        <linearGradient id={grad1} x1="0%" y1="0%" x2="100%" y2="100%">
           <stop offset="0%" stopColor="`#00e5ff`" />
           <stop offset="100%" stopColor="`#0099cc`" />
         </linearGradient>
-        <linearGradient id="tg2" x1="0%" y1="0%" x2="100%" y2="100%">
+        <linearGradient id={grad2} x1="0%" y1="0%" x2="100%" y2="100%">
           <stop offset="0%" stopColor="`#00aadd`" />
           <stop offset="100%" stopColor="`#006699`" />
         </linearGradient>
       </defs>
       {/* Outer cube outline */}
-      <path d="M40 6 L72 22 L72 58 L40 74 L8 58 L8 22 Z" fill="none" stroke="url(`#tg1`)" strokeWidth="1.5" opacity="0.6" />
+      <path d="M40 6 L72 22 L72 58 L40 74 L8 58 L8 22 Z" fill="none" stroke={`url(#${grad1})`} strokeWidth="1.5" opacity="0.6" />

Update all remaining url(#tg1) and url(#tg2) references to use the template literals {url(#${grad1})} and {url(#${grad2})}.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/TenetLogo.tsx` around lines 6 - 15, The TenetLogo
component uses hardcoded SVG gradient IDs ("tg1", "tg2") which collide when
multiple instances render; update TenetLogo to generate unique IDs per instance
(e.g., use React's useId()) and replace the hardcoded ids in the <linearGradient
id="..."> definitions and all gradient references (e.g., fill="url(`#tg1`)" /
fill="url(`#tg2`)") with the generated ids (e.g., `grad1`, `grad2`) so each
instance produces unique `url(#${grad1})` and `url(#${grad2})` references.

Comment thread landing/src/components/TerminalPanel.tsx
Comment thread landing/src/index.css Outdated
pavsoss and others added 6 commits June 10, 2026 21:35
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@pavsoss

pavsoss commented Jun 10, 2026

Copy link
Copy Markdown
Author

@S3DFX-CYBER Hey, Kindly review this and let me know if any changes are required

@S3DFX-CYBER

Copy link
Copy Markdown
Collaborator

It's good but I don't want it more vibe coded look now hence I wanted it redesigned properly
You can refer to these mockups which will help u to design a better landing page with less vibecoded look @pavsoss
6798267f-6589-49e4-b617-ed08858b5763.png

417001ee-9a73-4584-beb2-3effe9c09237.jpeg

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 9

♻️ Duplicate comments (2)
landing/src/components/InstallSection.tsx (2)

108-112: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle Clipboard API failures before showing “Copied”.

At Line 109, navigator.clipboard.writeText(cmd) is called without availability checks or rejection handling, but success UI is shown unconditionally on Lines 110-111. This can show false-positive copy status in unsupported or denied contexts.

Suggested fix
-  const handleCopy = (cmd: string) => {
-    navigator.clipboard.writeText(cmd);
-    setCopiedCmd(cmd);
-    setTimeout(() => setCopiedCmd(null), 2000);
-  };
+  const handleCopy = async (cmd: string) => {
+    try {
+      if (!navigator.clipboard?.writeText) return;
+      await navigator.clipboard.writeText(cmd);
+      setCopiedCmd(cmd);
+      setTimeout(() => setCopiedCmd(null), 2000);
+    } catch {
+      setCopiedCmd(null);
+    }
+  };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/InstallSection.tsx` around lines 108 - 112, The
handleCopy function calls navigator.clipboard.writeText(cmd) without checking
availability or handling rejections, then unconditionally sets copied state;
update handleCopy to first verify navigator.clipboard and
navigator.clipboard.writeText exist, call writeText(cmd) and await or then/catch
the promise, and only call setCopiedCmd(cmd) and schedule setCopiedCmd(null) on
successful resolution; on failure, log or surface the error and do not set
copied state (use the existing setCopiedCmd and timeout logic for success only).

139-150: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make copy control keyboard-accessible.

The copy control (Line 139) is an interactive div with only onClick, so keyboard users cannot reliably trigger copy via Enter/Space. This is an accessibility blocker for this flow.

Suggested fix
-                <div 
+                <button
+                  type="button"
                   className="install-cmd" 
                   onClick={() => handleCopy(d.cmd)}
-                  style={{ cursor: 'pointer' }}
                   title="Click to copy command"
-                >
+                  aria-label={`Copy install command: ${d.cmd}`}
+                >
                   <span className="install-prompt">$</span>
                   <span style={{ flex: 1 }}>{d.cmd}</span>
                   <span style={{ fontSize: '10px', color: 'var(--text3)', fontFamily: 'var(--mono)' }}>
                     {copiedCmd === d.cmd ? 'Copied' : 'Copy'}
                   </span>
-                </div>
+                </button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/InstallSection.tsx` around lines 139 - 150, The
interactive copy element in InstallSection.tsx (the div with className
"install-cmd") is not keyboard-accessible; add role="button" and tabIndex={0} to
that element and implement an onKeyDown handler that calls handleCopy(d.cmd)
when Enter or Space is pressed (also prevent default for Space). Keep the
existing onClick and title, and ensure the visual focus style remains visible;
no changes to copiedCmd logic are needed.
🧹 Nitpick comments (5)
landing/index.html (1)

20-23: ⚡ Quick win

Add twitter:image to complete the large-card metadata.

twitter:card=summary_large_image is set, but no explicit twitter:image is present. Add it to improve consistent social preview rendering.

Suggested diff
     <meta name="twitter:card" content="summary_large_image" />
     <meta name="twitter:title" content="TENET AI – Real-Time LLM Firewall" />
     <meta name="twitter:description" content="Detect prompt injections and protect agents with under 10ms overhead." />
+    <meta name="twitter:image" content="https://tenet.ai/og-image.png" />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/index.html` around lines 20 - 23, Add an explicit twitter:image meta
tag to complete the large-card Twitter metadata: update the head where
twitter:card, twitter:title, and twitter:description are defined (look for the
existing meta tags named "twitter:card", "twitter:title", and
"twitter:description") and add a corresponding <meta name="twitter:image"
content="..."> referencing your preferred social preview image URL so Twitter
consistently renders the summary_large_image card.
.gitignore (1)

207-210: ⚡ Quick win

Track package-lock.json instead of ignoring it.

Ignoring the lockfile makes frontend dependency resolution non-deterministic across environments. Keep node_modules/ ignored, but allow package-lock.json to be committed.

Suggested diff
 # Node.js / Frontend
 node_modules/
 dist/
-package-lock.json
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.gitignore around lines 207 - 210, Remove package-lock.json from the ignore
list so the frontend lockfile is tracked; edit the .gitignore entry that
currently lists "package-lock.json" (leave "node_modules/" and "dist/" ignored)
and then add/commit the package-lock.json to the repo so dependency resolution
is deterministic across environments.
landing/src/components/Navbar.tsx (1)

40-53: ⚡ Quick win

Harden GitHub stats fetch error handling.

Handle non-OK responses and abort on unmount to make this external call more resilient and avoid stale async work.

Suggested diff
-    fetch('https://api.github.com/repos/TENET-DEV-AI/TENET-AI')
-      .then(res => res.json())
+    const controller = new AbortController();
+    fetch('https://api.github.com/repos/TENET-DEV-AI/TENET-AI', { signal: controller.signal })
+      .then(res => {
+        if (!res.ok) throw new Error(`GitHub API returned ${res.status}`);
+        return res.json();
+      })
       .then(data => {
         if (data.stargazers_count !== undefined) {
           const s = data.stargazers_count;
           setStars(s >= 1000 ? (s / 1000).toFixed(1) + 'k' : String(s));
         }
         if (data.forks_count !== undefined) {
           const f = data.forks_count;
           setForks(f >= 1000 ? (f / 1000).toFixed(1) + 'k' : String(f));
         }
       })
       .catch(() => {});
@@
     return () => {
+      controller.abort();
       window.removeEventListener('keydown', handleKeyDown);
       observers.forEach(o => {
         if (o) o.obs.unobserve(o.el);
       });
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/Navbar.tsx` around lines 40 - 53, In the Navbar
component's GitHub fetch logic, make the request resilient by using an
AbortController and checking response.ok before reading JSON; if !response.ok
throw an error so the .catch can handle it, and on component unmount call
controller.abort to avoid stale async updates — ensure state setters setStars
and setForks are only called when the fetch hasn't been aborted (e.g., by
checking controller.signal.aborted or a mounted flag in the same useEffect) and
log or silently ignore errors in the catch; update the existing fetch block in
Navbar (the useEffect that calls fetch) to implement these changes.
landing/src/components/ComparisonTable.tsx (1)

62-63: ⚡ Quick win

Add a table caption for assistive-tech context.

Please add a <caption> (can be visually hidden) so screen-reader users get immediate table purpose/context without relying on surrounding layout text.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/ComparisonTable.tsx` around lines 62 - 63, The table
in the ComparisonTable component lacks a caption for screen-reader context; add
a <caption> element inside the <table className="compare-table"> (can be
visually hidden via an existing utility class or new .sr-only) that briefly
describes the table purpose (e.g., "Feature comparison of X and Y") so
assistive-tech users get immediate context; update the JSX in ComparisonTable
where the <table> is rendered to include this caption element.
landing/src/components/InstallSection.tsx (1)

73-103: 🏗️ Heavy lift

Root cause: duplicated FadeUp animation helper across landing/src/components/InstallSection.tsx and landing/src/components/CtaSection.tsx.
Both files define the same observer/transition wrapper locally. Extracting a shared landing/src/components/FadeUp.tsx and importing it from both files will prevent behavior drift and reduce maintenance overhead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/InstallSection.tsx` around lines 73 - 103, Duplicate
FadeUp animation helper exists in InstallSection.tsx and CtaSection.tsx; extract
it into a single shared component file named FadeUp.tsx and import it from both
places. Create a new functional component FadeUp that exports the same API
(props: children, delay?) and moves the useRef/useState/useEffect
IntersectionObserver logic and inline styles into that file, then replace the
local FadeUp definitions in both InstallSection and CtaSection with imports of
the new FadeUp component to avoid duplicated logic and ensure consistent
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@landing/src/App.tsx`:
- Around line 19-21: The skip link with class "skip-to-main" targets an element
with id "main-content" that is not focusable; update the target <main
id="main-content"> (or the element with that id) to be programmatically
focusable by adding tabIndex={-1} (or otherwise making it focusable) so that
activating the anchor actually moves keyboard focus into main content; ensure
the id matches "`#main-content`" and apply the same change for the other
occurrence referenced in the file.

In `@landing/src/components/DemoSection.tsx`:
- Around line 149-157: Tabs and tabpanel lack proper ARIA linkage: update the
tab element created in the ATTACKS.map (where you check const isActive =
attack.id === selectedId) to include a stable id (e.g., `tab-${attack.id}`) and
an aria-controls attribute that points to the corresponding panel id (e.g.,
`panel-${attack.id}`); then update the panel element (the element rendering the
selected attack content) to have id `panel-${attack.id}` and aria-labelledby
pointing back to `tab-${attack.id}` so screen readers can associate tabs with
their panels.
- Around line 103-105: In DemoSection, the analyze-phase setTimeout that calls
setStatus('done') is not tracked or cleared, which can apply a stale status to a
new run or fire after unmount; fix by storing the timeout id in a ref (e.g.,
analyzeTimeoutRef via useRef<number | null>), call
clearTimeout(analyzeTimeoutRef.current) before scheduling a new timeout and
assign the new id to the ref, and also clear it in the component cleanup
(useEffect return) or whenever starting a new animation; apply this change to
both the typing completion timeout locations that call setStatus('done').

In `@landing/src/components/Footer.tsx`:
- Around line 21-23: Change the forced smooth scrolling to respect the user's
reduced-motion preference: detect window.matchMedia('(prefers-reduced-motion:
reduce)').matches and set behavior to 'auto' when true, otherwise 'smooth';
apply this change in the scrollToTop function and the other programmatic scroll
handler in this file (the second scroll block around lines 27-32) so both use
the computed behavior while preserving existing calls to window.scrollTo(...)
and setAnnouncement(...).

In `@landing/src/components/Navbar.tsx`:
- Around line 69-76: The handler handleKeyDown should ignore the '/' shortcut
when the user is typing: add an early return that checks the current focused
element (use e.target or document.activeElement cast to HTMLElement) and if it's
an input, textarea, select, or any element with isContentEditable true (or
contentEditable !== 'false'), do not intercept the key; otherwise proceed to
query '.btn-primary', preventDefault, focus and scrollIntoView as before. Ensure
you update only the logic inside handleKeyDown so the rest of the flow (finding
'.btn-primary' and focusing it) remains unchanged.

In `@landing/src/components/TerminalPanel.tsx`:
- Around line 37-52: The effect in TerminalPanel that uses setTimeout to update
state (creating nextLog and calling setIsProcessing, setLines, setIdx and the
flash reset via setFlashRed) can schedule timeouts that fire after the component
unmounts; capture those timeout IDs (e.g. in a ref like timeoutsRef) when
calling setTimeout inside the useEffect([idx]) that contains the block using
LOGS and nextLog, and clear them in the component's cleanup/unmount handler
(clearTimeout for each stored id) or alternatively use a mounted/ref flag to
guard state updates before calling setState; ensure you clear all timeouts used
for the main update and the nested flash-red timeout so no state updates occur
after unmount.
- Line 114: The terminal log container currently uses an invalid aria-live value
("log") on the div with className "p-log"; replace aria-live="log" with
role="log" on that element (e.g., in TerminalPanel component) and add an
appropriate aria-relevant value such as aria-relevant="additions" so appended
entries are announced (role="log" implies polite live region and
aria-relevant="additions" ensures new items are announced).

In `@landing/src/index.css`:
- Around line 81-89: Rename all non-kebab `@keyframes` to kebab-case and update
their animation references: change keyframes fadeRow, fadeUp, heroFadeIn,
heroSlideUp16, heroFadeSlideUp8, heroSlideFromRight, processingDots,
packetTravel, drawLine to kebab-case (e.g., fade-row, fade-up, hero-fade-in,
hero-slide-up-16, hero-fade-slide-up-8, hero-slide-from-right, processing-dots,
packet-travel, draw-line) and update all animation: usages that reference them
(selectors .hero-enter-bg, .hero-enter-logo, .hero-enter-headline,
.hero-enter-sub, .hero-enter-cta1, .hero-enter-cta2, .hero-enter-pills,
.hero-enter-terminal and any at lines ~116, ~148, ~149 packetTravelBlock, and
the `@keyframes` block around lines 351–360) to use the new kebab-case names so
names match exactly between `@keyframes` and animation properties.
- Line 4: Replace the `@import` url('https://fonts.googleapis.com/...') statement
in landing/src/index.css with the configured import notation (use the plain
`@import` "https://fonts.googleapis.com/..." form) and change the CSS keyword
currentColor to lowercase currentcolor where it appears (reference the `@import`
statement and the token currentColor in the file); ensure both edits conform to
the project's stylelint import and keyword-case rules.

---

Duplicate comments:
In `@landing/src/components/InstallSection.tsx`:
- Around line 108-112: The handleCopy function calls
navigator.clipboard.writeText(cmd) without checking availability or handling
rejections, then unconditionally sets copied state; update handleCopy to first
verify navigator.clipboard and navigator.clipboard.writeText exist, call
writeText(cmd) and await or then/catch the promise, and only call
setCopiedCmd(cmd) and schedule setCopiedCmd(null) on successful resolution; on
failure, log or surface the error and do not set copied state (use the existing
setCopiedCmd and timeout logic for success only).
- Around line 139-150: The interactive copy element in InstallSection.tsx (the
div with className "install-cmd") is not keyboard-accessible; add role="button"
and tabIndex={0} to that element and implement an onKeyDown handler that calls
handleCopy(d.cmd) when Enter or Space is pressed (also prevent default for
Space). Keep the existing onClick and title, and ensure the visual focus style
remains visible; no changes to copiedCmd logic are needed.

---

Nitpick comments:
In @.gitignore:
- Around line 207-210: Remove package-lock.json from the ignore list so the
frontend lockfile is tracked; edit the .gitignore entry that currently lists
"package-lock.json" (leave "node_modules/" and "dist/" ignored) and then
add/commit the package-lock.json to the repo so dependency resolution is
deterministic across environments.

In `@landing/index.html`:
- Around line 20-23: Add an explicit twitter:image meta tag to complete the
large-card Twitter metadata: update the head where twitter:card, twitter:title,
and twitter:description are defined (look for the existing meta tags named
"twitter:card", "twitter:title", and "twitter:description") and add a
corresponding <meta name="twitter:image" content="..."> referencing your
preferred social preview image URL so Twitter consistently renders the
summary_large_image card.

In `@landing/src/components/ComparisonTable.tsx`:
- Around line 62-63: The table in the ComparisonTable component lacks a caption
for screen-reader context; add a <caption> element inside the <table
className="compare-table"> (can be visually hidden via an existing utility class
or new .sr-only) that briefly describes the table purpose (e.g., "Feature
comparison of X and Y") so assistive-tech users get immediate context; update
the JSX in ComparisonTable where the <table> is rendered to include this caption
element.

In `@landing/src/components/InstallSection.tsx`:
- Around line 73-103: Duplicate FadeUp animation helper exists in
InstallSection.tsx and CtaSection.tsx; extract it into a single shared component
file named FadeUp.tsx and import it from both places. Create a new functional
component FadeUp that exports the same API (props: children, delay?) and moves
the useRef/useState/useEffect IntersectionObserver logic and inline styles into
that file, then replace the local FadeUp definitions in both InstallSection and
CtaSection with imports of the new FadeUp component to avoid duplicated logic
and ensure consistent behavior.

In `@landing/src/components/Navbar.tsx`:
- Around line 40-53: In the Navbar component's GitHub fetch logic, make the
request resilient by using an AbortController and checking response.ok before
reading JSON; if !response.ok throw an error so the .catch can handle it, and on
component unmount call controller.abort to avoid stale async updates — ensure
state setters setStars and setForks are only called when the fetch hasn't been
aborted (e.g., by checking controller.signal.aborted or a mounted flag in the
same useEffect) and log or silently ignore errors in the catch; update the
existing fetch block in Navbar (the useEffect that calls fetch) to implement
these changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1503c8ba-90b2-4757-9f23-263412a0301a

📥 Commits

Reviewing files that changed from the base of the PR and between 1c1bcb4 and aa4e9f4.

📒 Files selected for processing (19)
  • .gitignore
  • landing/index.html
  • landing/src/App.tsx
  • landing/src/components/ArchitectureDiagram.tsx
  • landing/src/components/BreachCards.tsx
  • landing/src/components/ComparisonTable.tsx
  • landing/src/components/CtaSection.tsx
  • landing/src/components/DemoSection.tsx
  • landing/src/components/FeaturesSection.tsx
  • landing/src/components/Footer.tsx
  • landing/src/components/Hero.tsx
  • landing/src/components/InstallSection.tsx
  • landing/src/components/Navbar.tsx
  • landing/src/components/PipelineSection.tsx
  • landing/src/components/StatsSection.tsx
  • landing/src/components/TenetLogo.tsx
  • landing/src/components/TerminalPanel.tsx
  • landing/src/index.css
  • landing/src/main.tsx

Comment thread landing/src/App.tsx
Comment on lines +19 to +21
<a href="#main-content" className="skip-to-main">
Skip to main content
</a>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make the skip-link target programmatically focusable.

The skip link points to #main-content, but <main> isn’t focusable. This can prevent keyboard users from actually landing in main content after activating “Skip to main content”.

Suggested diff
-      <main id="main-content" className="main">
+      <main id="main-content" className="main" tabIndex={-1}>

Also applies to: 27-27

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/App.tsx` around lines 19 - 21, The skip link with class
"skip-to-main" targets an element with id "main-content" that is not focusable;
update the target <main id="main-content"> (or the element with that id) to be
programmatically focusable by adding tabIndex={-1} (or otherwise making it
focusable) so that activating the anchor actually moves keyboard focus into main
content; ensure the id matches "`#main-content`" and apply the same change for the
other occurrence referenced in the file.

Comment on lines +103 to +105
setTimeout(() => {
setStatus('done');
}, 1000);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear and deconflict the analyze-phase timeout.

The setTimeout in the typing completion path is not tracked or cleared, so rapid scenario switches can apply stale setStatus('done') to a newer run (and can still fire after unmount). Track this timeout in a ref and clear it before starting a new animation and in cleanup.

Suggested fix
+ const analyzeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);

  const triggerAnimation = (attack: AttackSample) => {
    setStatus('typing');
    setDisplayedPrompt('');
    if (typingTimer.current) clearInterval(typingTimer.current);
+   if (analyzeTimer.current) {
+     clearTimeout(analyzeTimer.current);
+     analyzeTimer.current = null;
+   }

    let charIdx = 0;
    const text = attack.prompt;

    typingTimer.current = setInterval(() => {
      setDisplayedPrompt(text.substring(0, charIdx + 1));
      charIdx++;
      if (charIdx >= text.length) {
        if (typingTimer.current) clearInterval(typingTimer.current);
        setStatus('analyzing');
-       setTimeout(() => {
+       analyzeTimer.current = setTimeout(() => {
          setStatus('done');
+         analyzeTimer.current = null;
        }, 1000);
      }
    }, 15);
  };

  useEffect(() => {
    triggerAnimation(activeAttack);
    return () => {
      if (typingTimer.current) clearInterval(typingTimer.current);
+     if (analyzeTimer.current) clearTimeout(analyzeTimer.current);
    };
  }, [selectedId]);

Also applies to: 110-115

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/DemoSection.tsx` around lines 103 - 105, In
DemoSection, the analyze-phase setTimeout that calls setStatus('done') is not
tracked or cleared, which can apply a stale status to a new run or fire after
unmount; fix by storing the timeout id in a ref (e.g., analyzeTimeoutRef via
useRef<number | null>), call clearTimeout(analyzeTimeoutRef.current) before
scheduling a new timeout and assign the new id to the ref, and also clear it in
the component cleanup (useEffect return) or whenever starting a new animation;
apply this change to both the typing completion timeout locations that call
setStatus('done').

Comment thread landing/src/components/DemoSection.tsx Outdated
Comment on lines +21 to +23
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
setAnnouncement('Scrolled back to top of the page.');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Respect reduced-motion preference in programmatic scrolling.

Both scroll paths force behavior: 'smooth', which ignores users who opt into reduced motion. Use 'auto' when prefers-reduced-motion: reduce is active.

As per coding guidelines, the acceptance criteria targets WCAG 2.1 AA and the PR summary explicitly calls out reduced-motion support.

Suggested patch
   const scrollToTop = () => {
-    window.scrollTo({ top: 0, behavior: 'smooth' });
+    const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
+    const behavior: ScrollBehavior = prefersReducedMotion ? 'auto' : 'smooth';
+    window.scrollTo({ top: 0, behavior });
     setAnnouncement('Scrolled back to top of the page.');
     setTimeout(() => setAnnouncement(''), 1000);
   };
@@
   const handleLinkClick = (e: React.MouseEvent<HTMLAnchorElement>, id: string) => {
     e.preventDefault();
     const el = document.getElementById(id);
     if (el) {
-      el.scrollIntoView({ behavior: 'smooth' });
+      const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
+      const behavior: ScrollBehavior = prefersReducedMotion ? 'auto' : 'smooth';
+      el.scrollIntoView({ behavior });
     }
   };

Also applies to: 27-32

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/Footer.tsx` around lines 21 - 23, Change the forced
smooth scrolling to respect the user's reduced-motion preference: detect
window.matchMedia('(prefers-reduced-motion: reduce)').matches and set behavior
to 'auto' when true, otherwise 'smooth'; apply this change in the scrollToTop
function and the other programmatic scroll handler in this file (the second
scroll block around lines 27-32) so both use the computed behavior while
preserving existing calls to window.scrollTo(...) and setAnnouncement(...).

Comment thread landing/src/components/Navbar.tsx Outdated
Comment on lines +37 to +52
setTimeout(() => {
setIsProcessing(false);
const nextLog: LogEntry = {
...LOGS[idx % LOGS.length],
id: `log-append-${idx}`
};

// 2. Add next log line
setLines(prev => [...prev.slice(-3), nextLog]);
setIdx(i => i + 1);

// 3. Flash border if blocked
if (nextLog.tag === 'BLOCKED') {
setFlashRed(true);
setTimeout(() => setFlashRed(false), 600);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="landing/src/components/TerminalPanel.tsx"
echo "== File: $FILE =="
ls -l "$FILE" || true

# Print relevant sections with line numbers
echo
echo "== Lines 1-120 =="
nl -ba "$FILE" | sed -n '1,120p'

echo
echo "== Search for setTimeout usage & cleanup =="
rg -n "setTimeout|clearTimeout|clearInterval|useEffect" "$FILE"

echo
echo "== Show effect block(s) =="
# Print around each useEffect
rg -n "useEffect\s*\(" "$FILE" | cut -d: -f1 | while read -r line; do
  start=$((line-10))
  end=$((line+80))
  echo "--- useEffect around line $line (show $start-$end) ---"
  nl -ba "$FILE" | sed -n "${start},${end}p"
done

Repository: TENET-DEV-AI/TENET-AI

Length of output: 289


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="landing/src/components/TerminalPanel.tsx"
echo "== File: $FILE =="
ls -l "$FILE" || true

echo
echo "== Lines 1-140 =="
cat -n "$FILE" | sed -n '1,140p'

echo
echo "== Search for setTimeout usage & cleanup =="
rg -n "setTimeout|clearTimeout|clearInterval|useEffect" "$FILE"

echo
echo "== Show effect block(s) around each useEffect =="
rg -n "useEffect\s*\(" "$FILE" | cut -d: -f1 | while read -r line; do
  start=$((line-10))
  end=$((line+90))
  echo "--- useEffect around line $line (show $start-$end) ---"
  cat -n "$FILE" | sed -n "${start},${end}p"
done

Repository: TENET-DEV-AI/TENET-AI

Length of output: 11679


Clear pending setTimeouts in TerminalPanel to avoid state updates after unmount.

  • landing/src/components/TerminalPanel.tsx cleanup at line 66 only clears the interval; the timeouts at lines 37 and 51 are not cleared.
  • Store the timeout IDs (or guard state updates with a mounted/ref flag) and clear/disable them in an unmount-only cleanup, since this useEffect([idx]) also re-runs on idx changes (a naive timeout clear there would cancel the intended flashRed delay).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/TerminalPanel.tsx` around lines 37 - 52, The effect in
TerminalPanel that uses setTimeout to update state (creating nextLog and calling
setIsProcessing, setLines, setIdx and the flash reset via setFlashRed) can
schedule timeouts that fire after the component unmounts; capture those timeout
IDs (e.g. in a ref like timeoutsRef) when calling setTimeout inside the
useEffect([idx]) that contains the block using LOGS and nextLog, and clear them
in the component's cleanup/unmount handler (clearTimeout for each stored id) or
alternatively use a mounted/ref flag to guard state updates before calling
setState; ensure you clear all timeouts used for the main update and the nested
flash-red timeout so no state updates occur after unmount.

Comment thread landing/src/components/TerminalPanel.tsx Outdated
Comment thread landing/src/index.css Outdated
/* ═══════════════════════════════════════════════════════════════
TENET AI v2 — Landing Page Styles
═══════════════════════════════════════════════════════════════ */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@300;400;500;600&display=swap');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the two specific stylelint findings are resolved
rg -n "`@import` url\(" landing/src/index.css
rg -n "currentColor" landing/src/index.css

Repository: TENET-DEV-AI/TENET-AI

Length of output: 270


Fix remaining stylelint import/keyword casing in landing/src/index.css (lines 4, 251).

  • Line 4: @import url(...) should use the configured import notation.
  • Line 251: currentColor should match configured keyword case (currentcolor).
Suggested patch
-@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@300;400;500;600&display=swap');
+@import 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@300;400;500;600&display=swap';
...
-.progress-pill.filled{background:currentColor}
+.progress-pill.filled{background:currentcolor}
🧰 Tools
🪛 Stylelint (17.12.0)

[error] 4-4: Expected "url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@300;400;500;600&display=swap')" to be "'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@300;400;500;600&display=swap'" (import-notation)

(import-notation)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/index.css` at line 4, Replace the `@import`
url('https://fonts.googleapis.com/...') statement in landing/src/index.css with
the configured import notation (use the plain `@import`
"https://fonts.googleapis.com/..." form) and change the CSS keyword currentColor
to lowercase currentcolor where it appears (reference the `@import` statement and
the token currentColor in the file); ensure both edits conform to the project's
stylelint import and keyword-case rules.

Source: Linters/SAST tools

Comment thread landing/src/index.css Outdated
Comment on lines +81 to +89
.hero-enter-bg{opacity:0;animation:heroFadeIn 0.4s ease both}
.hero-enter-logo{opacity:0;animation:heroFadeSlideUp8 0.6s ease-out both;animation-delay:100ms}
.hero-enter-headline{opacity:0;animation:heroSlideUp16 0.5s ease-out both;animation-delay:200ms}
.hero-enter-sub{opacity:0;animation:heroFadeIn 0.4s ease both;animation-delay:400ms}
.hero-enter-cta1{opacity:0;animation:heroFadeIn 0.4s ease both;animation-delay:600ms}
.hero-enter-cta2{opacity:0;animation:heroFadeIn 0.4s ease both;animation-delay:800ms}
.hero-enter-pills{opacity:0;animation:heroFadeIn 0.4s ease both;animation-delay:800ms}
.hero-enter-terminal{opacity:0;animation:heroSlideFromRight 0.6s ease-out both;animation-delay:1000ms}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find non-kebab keyframes and their animation call-sites in this file
rg -n "`@keyframes`\s+[A-Za-z0-9]*[A-Z][A-Za-z0-9]*" landing/src/index.css
rg -n "animation:\s*[A-Za-z0-9]*[A-Z][A-Za-z0-9]*" landing/src/index.css

Repository: TENET-DEV-AI/TENET-AI

Length of output: 2226


Rename camelCase/PascalCase @keyframes to kebab-case and update all animation: references (landing/src/index.css).

Non-kebab @keyframes declarations exist at lines 351–360 (fadeRow, fadeUp, heroFadeIn, heroSlideUp16, heroFadeSlideUp8, heroSlideFromRight, processingDots, packetTravel, drawLine) with matching animation: call sites at lines 81–88, 116, 148, and 361. Also update the packetTravelBlock animation reference at line 149 to match its (kebab-cased) @keyframes name if applicable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/index.css` around lines 81 - 89, Rename all non-kebab `@keyframes`
to kebab-case and update their animation references: change keyframes fadeRow,
fadeUp, heroFadeIn, heroSlideUp16, heroFadeSlideUp8, heroSlideFromRight,
processingDots, packetTravel, drawLine to kebab-case (e.g., fade-row, fade-up,
hero-fade-in, hero-slide-up-16, hero-fade-slide-up-8, hero-slide-from-right,
processing-dots, packet-travel, draw-line) and update all animation: usages that
reference them (selectors .hero-enter-bg, .hero-enter-logo,
.hero-enter-headline, .hero-enter-sub, .hero-enter-cta1, .hero-enter-cta2,
.hero-enter-pills, .hero-enter-terminal and any at lines ~116, ~148, ~149
packetTravelBlock, and the `@keyframes` block around lines 351–360) to use the new
kebab-case names so names match exactly between `@keyframes` and animation
properties.

Source: Linters/SAST tools

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
landing/src/components/TerminalPanel.tsx (1)

42-85: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stabilize the feed effect lifecycle (interval reset + uncleared timeouts).

idx in the dependency array causes the interval to be recreated every tick, and inner setTimeouts aren’t cleared on unmount. This produces unstable cadence and can trigger post-unmount state updates.

Suggested fix
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useRef } from 'react';
...
-  const [idx, setIdx] = useState(4);
+  const idxRef = useRef(4);
+  const timeoutsRef = useRef<number[]>([]);
...
-  useEffect(() => {
+  useEffect(() => {
     const interval = setInterval(() => {
       if (document.hidden) return; // Prevent background tab issues
       
       setIsProcessing(true);
       
-      setTimeout(() => {
+      const t1 = window.setTimeout(() => {
         setIsProcessing(false);
         const nextLog: LogEntry = {
-          ...LOGS[idx % LOGS.length],
-          id: `log-append-${idx}`
+          ...LOGS[idxRef.current % LOGS.length],
+          id: `log-append-${idxRef.current}`
         };
         
         setLines(prev => [nextLog, ...prev].slice(0, 4));
-        setIdx(i => i + 1);
+        idxRef.current += 1;
...
         if (nextLog.tag === 'BLOCKED') {
           setFlashRed(true);
           setStats(s => ({ ...s, blocked: s.blocked + 1 }));
-          setTimeout(() => setFlashRed(false), 600);
+          const t2 = window.setTimeout(() => setFlashRed(false), 600);
+          timeoutsRef.current.push(t2);
         } else if (nextLog.tag === 'FLAGGED') {
           setStats(s => ({ ...s, flagged: s.flagged + 1 }));
         } else if (nextLog.tag === 'ALLOWED') {
           setStats(s => ({ ...s, allowed: s.allowed + 1 }));
         }
...
-      }, 300);
+      }, 300);
+      timeoutsRef.current.push(t1);
 
     }, 1400); // 1.4s feed interval
 
-    return () => clearInterval(interval);
-  }, [idx]);
+    return () => {
+      clearInterval(interval);
+      timeoutsRef.current.forEach(clearTimeout);
+      timeoutsRef.current = [];
+    };
+  }, []);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/TerminalPanel.tsx` around lines 42 - 85, The effect
recreates the interval every tick because idx is in the dependency array and
inner setTimeouts aren’t cleared; change the useEffect so it has an empty
dependency array and manage mutable state with refs: introduce an idxRef to
read/write the current index instead of depending on idx, call setIdx only for
external state consistency via functional updater when needed, capture interval
ID and any inner timeout IDs in refs (e.g., intervalRef, timeoutRefs) so you can
clearInterval(intervalRef.current) and clearTimeout for each timeout on cleanup,
and ensure all callbacks inside the interval use ref values (idxRef.current) and
the existing setters (setIsProcessing, setLines, setBars, setStats, setFlashRed)
via functional updates to avoid stale closures; this stabilizes cadence and
prevents post-unmount state updates.
🧹 Nitpick comments (1)
landing/src/components/InteractivePieChart.tsx (1)

9-14: ⚡ Quick win

Avoid duplicating scenario IDs/colors across files.

SLICES duplicates contract data already defined in landing/src/components/DemoSection.tsx (ATTACKS). A future ID/color drift will silently break activeId highlighting and click-selection mapping.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/InteractivePieChart.tsx` around lines 9 - 14, The
SLICES constant duplicates the same attack IDs/colors defined elsewhere
(ATTACKS), which risks drifting IDs and breaking selection/highlighting; replace
the local SLICES with a single source of truth by importing the existing ATTACKS
definition and mapping it to the shape used by InteractivePieChart (or
re-exporting a shared constant) so both components use the same IDs/colors;
remove the local SLICES declaration, ensure interactive props (activeId, click
handlers) still reference the shared IDs, and adapt any differences (e.g.,
visualPercent vs percent) by deriving those fields from ATTACKS where needed so
highlighting/click mapping remains consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@landing/src/components/DemoSection.tsx`:
- Around line 189-197: The aria-selected prop is using isActive (which derives
from activeId = hoveredId || selectedId) and thus reflects hover instead of the
actual selected tab; change aria-selected to use isSelected (attack.id ===
selectedId) so screen readers see the real selection, while leaving the
hover-driven styling that uses isActive/className intact; update the JSX where
aria-selected is set to use isSelected and ensure isSelected is computed as
attack.id === selectedId (keeping isActive, activeId, hoveredId, and selectedId
logic as-is).

In `@landing/src/components/InteractivePieChart.tsx`:
- Around line 50-63: The slice wrapper div in InteractivePieChart (the element
using onMouseEnter/onMouseLeave/onClick, position:'absolute', inset:0) is
intercepting pointer events because all wrappers overlap; move the event
handlers off that full-size wrapper and onto the actual SVG arc element (the
circle/path rendered around lines ~81–92) and set the wrapper's style to
pointerEvents: 'none' so only the visible arc receives hits (keep pointerEvents:
'auto' on the SVG element). Also stop hardcoding SLICES inside
InteractivePieChart (which currently shares ids/colors with
DemoSection/ATTACKS); instead accept slices as a prop to InteractivePieChart (or
rename SLICES to a locally unique constant) and update usages of the SLICES
symbol accordingly so ids/colors cannot drift or collide.

In `@landing/src/components/UnifiedArchitectureSection.tsx`:
- Line 33: The architecture SVGs in UnifiedArchitectureSection.tsx lack
accessible names; update each SVG element (the one at the shown diff and the
other SVGs in this component) to include role="img" and aria-labelledby
referencing unique ids, then add matching <title id="..."> and <desc id="...">
elements as the first children of each SVG (e.g., title id="architecture-title"
and desc id="architecture-desc", ensuring ids are unique per SVG); keep the
descriptive text concise and meaningful for screen readers and ensure the
aria-labelledby value lists both title and desc ids.

---

Duplicate comments:
In `@landing/src/components/TerminalPanel.tsx`:
- Around line 42-85: The effect recreates the interval every tick because idx is
in the dependency array and inner setTimeouts aren’t cleared; change the
useEffect so it has an empty dependency array and manage mutable state with
refs: introduce an idxRef to read/write the current index instead of depending
on idx, call setIdx only for external state consistency via functional updater
when needed, capture interval ID and any inner timeout IDs in refs (e.g.,
intervalRef, timeoutRefs) so you can clearInterval(intervalRef.current) and
clearTimeout for each timeout on cleanup, and ensure all callbacks inside the
interval use ref values (idxRef.current) and the existing setters
(setIsProcessing, setLines, setBars, setStats, setFlashRed) via functional
updates to avoid stale closures; this stabilizes cadence and prevents
post-unmount state updates.

---

Nitpick comments:
In `@landing/src/components/InteractivePieChart.tsx`:
- Around line 9-14: The SLICES constant duplicates the same attack IDs/colors
defined elsewhere (ATTACKS), which risks drifting IDs and breaking
selection/highlighting; replace the local SLICES with a single source of truth
by importing the existing ATTACKS definition and mapping it to the shape used by
InteractivePieChart (or re-exporting a shared constant) so both components use
the same IDs/colors; remove the local SLICES declaration, ensure interactive
props (activeId, click handlers) still reference the shared IDs, and adapt any
differences (e.g., visualPercent vs percent) by deriving those fields from
ATTACKS where needed so highlighting/click mapping remains consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6df130f9-00f7-4dc8-b097-b425122517fb

📥 Commits

Reviewing files that changed from the base of the PR and between aa4e9f4 and f2d69a8.

📒 Files selected for processing (13)
  • landing/src/App.tsx
  • landing/src/components/BreachCards.tsx
  • landing/src/components/ComparisonTable.tsx
  • landing/src/components/CtaSection.tsx
  • landing/src/components/DemoSection.tsx
  • landing/src/components/Footer.tsx
  • landing/src/components/Hero.tsx
  • landing/src/components/InstallSection.tsx
  • landing/src/components/InteractivePieChart.tsx
  • landing/src/components/Navbar.tsx
  • landing/src/components/TerminalPanel.tsx
  • landing/src/components/UnifiedArchitectureSection.tsx
  • landing/src/index.css
🚧 Files skipped from review as they are similar to previous changes (7)
  • landing/src/components/CtaSection.tsx
  • landing/src/components/ComparisonTable.tsx
  • landing/src/App.tsx
  • landing/src/components/BreachCards.tsx
  • landing/src/components/Hero.tsx
  • landing/src/components/InstallSection.tsx
  • landing/src/components/Footer.tsx

Comment thread landing/src/components/DemoSection.tsx
Comment thread landing/src/components/InteractivePieChart.tsx
{/* TOPOLOGY DIAGRAM */}
<div className="arch-diagram-card">
<div style={{ minWidth: '950px', position: 'relative', padding: '0 40px' }}>
<svg width="100%" viewBox="0 0 1080 500" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ overflow: 'visible' }}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add accessible naming for the architecture SVG.

This diagram is content-critical but has no explicit accessible name/description. On Line 33, add role="img" plus aria-labelledby, and include <title>/<desc> inside the SVG so screen-reader users get equivalent context.

Suggested patch
-            <svg width="100%" viewBox="0 0 1080 500" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ overflow: 'visible' }}>
+            <svg
+              width="100%"
+              viewBox="0 0 1080 500"
+              fill="none"
+              xmlns="http://www.w3.org/2000/svg"
+              style={{ overflow: 'visible' }}
+              role="img"
+              aria-labelledby="system-topology-title system-topology-desc"
+            >
+              <title id="system-topology-title">TENET AI system topology and decision pipeline</title>
+              <desc id="system-topology-desc">
+                Requests flow from application inputs into TENET SDK, through parallel detection engines, then into policy decisions:
+                block, flag, sanitize, or allow.
+              </desc>

Also applies to: 59-91, 117-223

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/UnifiedArchitectureSection.tsx` at line 33, The
architecture SVGs in UnifiedArchitectureSection.tsx lack accessible names;
update each SVG element (the one at the shown diff and the other SVGs in this
component) to include role="img" and aria-labelledby referencing unique ids,
then add matching <title id="..."> and <desc id="..."> elements as the first
children of each SVG (e.g., title id="architecture-title" and desc
id="architecture-desc", ensuring ids are unique per SVG); keep the descriptive
text concise and meaningful for screen readers and ensure the aria-labelledby
value lists both title and desc ids.

@pavsoss

pavsoss commented Jun 13, 2026

Copy link
Copy Markdown
Author

@S3DFX-CYBER Please review it now

Changes Made

Added / Replaced Components:

  • Added UnifiedArchitectureSection.tsx to replace the old pipeline/architecture flow.
  • Added InteractivePieChart.tsx for dynamic threat data visualization.
  • Deleted ArchitectureDiagram.tsx and PipelineSection.tsx.

Styling & Responsiveness:

  • Migrated inline styles to index.css.
  • Added comprehensive media queries in index.css for mobile (<500px), tablet (<768px), and small desktop (<1024px) to fix horizontal scrolling and layout breaking.
  • Refactored layouts in Hero.tsx, BreachCards.tsx, CtaSection.tsx, StatsSection.tsx, Navbar.tsx, DemoSection.tsx, and InstallSection.tsx to use the new global CSS classes.

Final Lighthouse Scores

  • Performance: 92 / 100
  • Accessibility: 96 / 100
  • Best Practices: 100 / 100
  • SEO: 91 / 100

Screenshots

Dashboard

image

Pipeline

image

Features

image

Live Demo

image

Compare

image

Case Study

image

Install

image

CTA

image

@vercel

vercel Bot commented Jun 13, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
tenet-ai.org Ready Ready Preview, Comment Jun 13, 2026 12:23pm

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Redesign and Modernize TENET AI Landing Page

2 participants