Redesign landing page#158
Conversation
…NET-AI into redesign-landing-page
|
@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. |
📝 WalkthroughWalkthroughComplete 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. ChangesLanding Page Redesign & Component Architecture
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.
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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>
| const [bars, setBars] = useState([78, 14, 6, 2]); // distribution percentages | ||
|
|
||
| useEffect(() => { | ||
| const interval = setInterval(() => { |
There was a problem hiding this comment.
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%"> |
There was a problem hiding this comment.
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>
|
Caution Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted. Error details |
There was a problem hiding this comment.
Actionable comments posted: 18
🧹 Nitpick comments (15)
landing/src/App.tsx (1)
27-27: ⚡ Quick winMake 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 winExpose 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 winKeep 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 winConsider 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-checkselector 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 valueConsider renaming CSS class for component independence.
The class name
hero-cursorsuggests coupling to the Hero component. For better component independence and maintainability, consider using a more generic name liketyping-cursororblink-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 winMove inline CSS to stylesheet for better separation of concerns.
Using
dangerouslySetInnerHTMLto 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 tolanding/src/index.cssfor 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 valueConsider 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 winRedundant inline styles on screen-reader element.
The element already has
className="sr-only", so the inlinestyleobject duplicates styles that should be handled by the.sr-onlyCSS 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 tradeoffConsider 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 winInconsistent SVG viewBox dimensions.
The star icon has
width="10" height="10"butviewBox="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 winExtract 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 winClarify abbreviated property names.
The properties
landcin the tag objects are cryptic. Use descriptive nameslabelandcolorto 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 winConsider 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 valueRedundant 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 valueConsider 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
📒 Files selected for processing (19)
.gitignorelanding/index.htmllanding/src/App.tsxlanding/src/components/ArchitectureDiagram.tsxlanding/src/components/BreachCards.tsxlanding/src/components/ComparisonTable.tsxlanding/src/components/CtaSection.tsxlanding/src/components/DemoSection.tsxlanding/src/components/FeaturesSection.tsxlanding/src/components/Footer.tsxlanding/src/components/Hero.tsxlanding/src/components/InstallSection.tsxlanding/src/components/Navbar.tsxlanding/src/components/PipelineSection.tsxlanding/src/components/StatsSection.tsxlanding/src/components/TenetLogo.tsxlanding/src/components/TerminalPanel.tsxlanding/src/index.csslanding/src/main.tsx
| 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> | ||
| ); | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
| {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' }}> |
There was a problem hiding this comment.
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.
| @@ -0,0 +1,45 @@ | |||
| import React from 'react'; | |||
|
|
|||
| export default function TenetLogo({ size = 32 }) { | |||
There was a problem hiding this comment.
🛠️ 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.
| 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.
| <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> |
There was a problem hiding this comment.
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.
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>
|
@S3DFX-CYBER Hey, Kindly review this and let me know if any changes are required |
|
It's good but I don't want it more vibe coded look now hence I wanted it redesigned properly |
There was a problem hiding this comment.
Actionable comments posted: 9
♻️ Duplicate comments (2)
landing/src/components/InstallSection.tsx (2)
108-112:⚠️ Potential issue | 🟠 Major | ⚡ Quick winHandle 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 winMake copy control keyboard-accessible.
The copy control (Line 139) is an interactive
divwith onlyonClick, 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 winAdd
twitter:imageto complete the large-card metadata.
twitter:card=summary_large_imageis set, but no explicittwitter:imageis 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 winTrack
package-lock.jsoninstead of ignoring it.Ignoring the lockfile makes frontend dependency resolution non-deterministic across environments. Keep
node_modules/ignored, but allowpackage-lock.jsonto 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 winHarden 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 winAdd 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 liftRoot cause: duplicated
FadeUpanimation helper acrosslanding/src/components/InstallSection.tsxandlanding/src/components/CtaSection.tsx.
Both files define the same observer/transition wrapper locally. Extracting a sharedlanding/src/components/FadeUp.tsxand 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
📒 Files selected for processing (19)
.gitignorelanding/index.htmllanding/src/App.tsxlanding/src/components/ArchitectureDiagram.tsxlanding/src/components/BreachCards.tsxlanding/src/components/ComparisonTable.tsxlanding/src/components/CtaSection.tsxlanding/src/components/DemoSection.tsxlanding/src/components/FeaturesSection.tsxlanding/src/components/Footer.tsxlanding/src/components/Hero.tsxlanding/src/components/InstallSection.tsxlanding/src/components/Navbar.tsxlanding/src/components/PipelineSection.tsxlanding/src/components/StatsSection.tsxlanding/src/components/TenetLogo.tsxlanding/src/components/TerminalPanel.tsxlanding/src/index.csslanding/src/main.tsx
| <a href="#main-content" className="skip-to-main"> | ||
| Skip to main content | ||
| </a> |
There was a problem hiding this comment.
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.
| setTimeout(() => { | ||
| setStatus('done'); | ||
| }, 1000); |
There was a problem hiding this comment.
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').
| const scrollToTop = () => { | ||
| window.scrollTo({ top: 0, behavior: 'smooth' }); | ||
| setAnnouncement('Scrolled back to top of the page.'); |
There was a problem hiding this comment.
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(...).
| 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); | ||
| } |
There was a problem hiding this comment.
🧩 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"
doneRepository: 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"
doneRepository: TENET-DEV-AI/TENET-AI
Length of output: 11679
Clear pending setTimeouts in TerminalPanel to avoid state updates after unmount.
landing/src/components/TerminalPanel.tsxcleanup 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 onidxchanges (a naive timeout clear there would cancel the intendedflashReddelay).
🤖 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.
| /* ═══════════════════════════════════════════════════════════════ | ||
| 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'); |
There was a problem hiding this comment.
🧩 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.cssRepository: 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:
currentColorshould 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
| .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} | ||
|
|
There was a problem hiding this comment.
🧩 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.cssRepository: 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
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
landing/src/components/TerminalPanel.tsx (1)
42-85:⚠️ Potential issue | 🟠 Major | ⚡ Quick winStabilize the feed effect lifecycle (interval reset + uncleared timeouts).
idxin the dependency array causes the interval to be recreated every tick, and innersetTimeouts 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 winAvoid duplicating scenario IDs/colors across files.
SLICESduplicates contract data already defined inlanding/src/components/DemoSection.tsx(ATTACKS). A future ID/color drift will silently breakactiveIdhighlighting 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
📒 Files selected for processing (13)
landing/src/App.tsxlanding/src/components/BreachCards.tsxlanding/src/components/ComparisonTable.tsxlanding/src/components/CtaSection.tsxlanding/src/components/DemoSection.tsxlanding/src/components/Footer.tsxlanding/src/components/Hero.tsxlanding/src/components/InstallSection.tsxlanding/src/components/InteractivePieChart.tsxlanding/src/components/Navbar.tsxlanding/src/components/TerminalPanel.tsxlanding/src/components/UnifiedArchitectureSection.tsxlanding/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
| {/* 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' }}> |
There was a problem hiding this comment.
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.
|
@S3DFX-CYBER Please review it now Changes MadeAdded / Replaced Components:
Styling & Responsiveness:
Final Lighthouse Scores
ScreenshotsDashboard
Pipeline
Features
Live Demo
Compare
Case Study
Install
CTA
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|










Summary
Upgrade of the TENET AI landing page to v2. This includes modularizing the monolithic
App.tsxlayout 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
App.tsxinto decoupled components underlanding/src/components(includingHero,Navbar,Footer,TerminalPanel,DemoSection,ComparisonTable,BreachCards,StatsSection,PipelineSection,ArchitectureDiagram, andTenetLogo).landing/src/index.cssusing the locked color palette, full support forprefers-reduced-motionoptions, and skip-to-main accessibility links..compare-tableto usetable-layout: fixedand set proportional column percentage widths to align column cells and headers perfectly.TerminalPanel.tsxlogs to use persistent unique IDs (line.id), which ensures only new arriving log rows trigger the slide-in/fade-in entrance transition.1181px-1380px) to shrink padding and hide icons to prevent topbar layout collisions.Related Issue
Fixes #121
Type of Change
Screenshots
Checklist
Additional Notes (Optional)
dist/index.html,dist/assets/index.css,dist/assets/index.js).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
landing/index.html.Refactors
landing/src/index.csswith accessible defaults,prefers-reduced-motion, improved contrast/focus, and refined breakpoints; imported inlanding/src/main.tsx.table-layout: fixed..gitignoreto ignore frontend artifacts (node_modules,dist,package-lock.json).Written for commit ce9fdd6. Summary will update on new commits.
Summary by CodeRabbit
New Features
Style
Chores