Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions apps/example/src/components/docs-mdx/audience.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { HTMLAttributes, ReactNode } from "react";

export type AudienceTarget = "agent" | "human";

export type AudienceProps = HTMLAttributes<HTMLDivElement> & {
target: AudienceTarget;
children?: ReactNode;
};

export function Audience({ target, children, ...rest }: AudienceProps) {
if (target === "agent") {
return null;
}

return (
<div data-leadtype-audience={target} {...rest}>
{children}
</div>
);
}
143 changes: 143 additions & 0 deletions apps/example/src/components/docs-mdx/file-tree.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import type {
DetailsHTMLAttributes,
HTMLAttributes,
LiHTMLAttributes,
ReactNode,
} from "react";
import { useState } from "react";

function FolderIcon() {
return (
<svg
aria-hidden="true"
data-leadtype-file-tree-icon="folder"
fill="none"
height="14"
viewBox="0 0 16 16"
width="14"
>
<path
d="M1.75 3.5h4.379a.75.75 0 0 1 .53.22L8.19 5.25h6.06a.75.75 0 0 1 .75.75v6.5a.75.75 0 0 1-.75.75H1.75a.75.75 0 0 1-.75-.75v-8.25a.75.75 0 0 1 .75-.75Z"
fill="currentColor"
fillOpacity="0.16"
stroke="currentColor"
strokeWidth="1.1"
/>
</svg>
);
}

function FileIcon() {
return (
<svg
aria-hidden="true"
data-leadtype-file-tree-icon="file"
fill="none"
height="14"
viewBox="0 0 16 16"
width="14"
>
<path
d="M3.75 2h5.5L13 5.75v8.5a.75.75 0 0 1-.75.75h-8.5a.75.75 0 0 1-.75-.75v-11.5A.75.75 0 0 1 3.75 2Z"
fill="currentColor"
fillOpacity="0.08"
stroke="currentColor"
strokeWidth="1.1"
/>
<path
d="M9.25 2v3.25a.5.5 0 0 0 .5.5H13"
stroke="currentColor"
strokeLinecap="round"
strokeWidth="1.1"
/>
</svg>
);
}

function ChevronIcon() {
return (
<svg
aria-hidden="true"
data-leadtype-file-tree-chevron=""
fill="none"
height="10"
viewBox="0 0 16 16"
width="10"
>
<path
d="m6 4 4 4-4 4"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.6"
/>
</svg>
);
}

export type FileTreeProps = HTMLAttributes<HTMLDivElement> & {
root?: string;
children?: ReactNode;
};

export function FileTree({ root, children, ...rest }: FileTreeProps) {
return (
<div data-leadtype-file-tree="" {...rest}>
{root ? (
<div data-leadtype-file-tree-root="">
<FolderIcon />
<span data-leadtype-file-tree-name="">{root}/</span>
</div>
) : null}
<ul data-leadtype-file-tree-list="">{children}</ul>
</div>
);
}

export type FolderProps = DetailsHTMLAttributes<HTMLDetailsElement> & {
name: string;
defaultOpen?: boolean;
children?: ReactNode;
};

export function Folder({
name,
defaultOpen = true,
children,
...rest
}: FolderProps) {
const [isOpen, setIsOpen] = useState(defaultOpen);

return (
<li data-leadtype-file-tree-item="">
<details
data-leadtype-file-tree-folder=""
onToggle={(event) =>
setIsOpen((event.target as HTMLDetailsElement).open)
}
open={isOpen}
{...rest}
Comment on lines +97 to +119
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cd /tmp && find . -type f -name "file-tree.tsx" 2>/dev/null | head -20

Repository: inthhq/leadtype

Length of output: 41


🏁 Script executed:

find . -type f -name "file-tree.tsx" 2>/dev/null

Repository: inthhq/leadtype

Length of output: 112


🏁 Script executed:

git ls-files | grep file-tree.tsx

Repository: inthhq/leadtype

Length of output: 110


🏁 Script executed:

find . -path "*/components/docs-mdx/*" -type f 2>/dev/null

Repository: inthhq/leadtype

Length of output: 890


🏁 Script executed:

git ls-files | grep -E "(docs-mdx|file-tree)" | head -20

Repository: inthhq/leadtype

Length of output: 915


🏁 Script executed:

cat -n apps/example/src/components/docs-mdx/file-tree.tsx | head -130

Repository: inthhq/leadtype

Length of output: 3927


🏁 Script executed:

wc -l apps/example/src/components/docs-mdx/file-tree.tsx

Repository: inthhq/leadtype

Length of output: 114


🏁 Script executed:

cat -n apps/example/src/components/docs-mdx/file-tree.tsx | sed -n '103,130p'

Repository: inthhq/leadtype

Length of output: 943


🏁 Script executed:

rg -A 5 "onToggle" apps/example/src/components/docs-mdx/file-tree.tsx

Repository: inthhq/leadtype

Length of output: 209


🏁 Script executed:

# Check if there are any other usages of Folder component to understand the impact
rg "Folder\s*\{" apps/example/src --type tsx -B 2 -A 5 | head -40

Repository: inthhq/leadtype

Length of output: 87


🏁 Script executed:

rg "Folder\s*\{" apps/example/src -t ts -B 2 -A 5 | head -60

Repository: inthhq/leadtype

Length of output: 41


🏁 Script executed:

rg "<Folder" apps/example/src --type-list | grep -i tsx

Repository: inthhq/leadtype

Length of output: 449


🏁 Script executed:

rg "<Folder" apps/example/src -A 3 | head -50

Repository: inthhq/leadtype

Length of output: 50374


🏁 Script executed:

rg "import.*Folder" apps/example/src --type ts | head -20

Repository: inthhq/leadtype

Length of output: 168


🏁 Script executed:

rg "<Folder" apps/example/src --max-count=10

Repository: inthhq/leadtype

Length of output: 50374


🏁 Script executed:

find apps/example/src -name "*.mdx" -o -name "*.md" | xargs grep -l "Folder" 2>/dev/null | head -5

Repository: inthhq/leadtype

Length of output: 41


🏁 Script executed:

# Check if there are other TypeScript/TSX files using the Folder component
rg "Folder\s*name=" apps/example --max-count=5

Repository: inthhq/leadtype

Length of output: 50374


🏁 Script executed:

cat > /tmp/test_event.ts << 'EOF'
import type { ChangeEvent } from "react";

// Test 1: event.target needs assertion for HTMLDetailsElement
function test1(event: React.SyntheticEvent<HTMLDetailsElement>) {
  const open1 = (event.target as HTMLDetailsElement).open;  // Has type assertion
  const open2 = event.currentTarget.open;  // No assertion needed, typed correctly
}

// Test 2: ToggleEvent on details
function test2(event: React.SyntheticEvent<HTMLDetailsElement>) {
  // event.target is EventTarget (untyped)
  // event.currentTarget is HTMLDetailsElement (typed)
  console.log(event.currentTarget.open);  // Works without assertion
}
EOF
cat /tmp/test_event.ts

Repository: inthhq/leadtype

Length of output: 668


Prevent prop override: omit open/onToggle from FolderProps.

FolderProps currently extends DetailsHTMLAttributes<HTMLDetailsElement>, and {...rest} is spread after the internal open and onToggle props. Since JSX applies props left-to-right, caller-supplied open or onToggle in rest will override the internal state management, breaking the component's ability to track folder open/closed state.

Additionally, the onToggle handler uses event.target as HTMLDetailsElement — a type assertion that violates the coding guideline to leverage type narrowing. event.currentTarget is properly typed without needing a cast.

Proposed fix
-export type FolderProps = DetailsHTMLAttributes<HTMLDetailsElement> & {
+export type FolderProps = Omit<
+  DetailsHTMLAttributes<HTMLDetailsElement>,
+  "open" | "onToggle"
+> & {
   name: string;
   defaultOpen?: boolean;
   children?: ReactNode;
 };
@@
       <details
         data-leadtype-file-tree-folder=""
-        onToggle={(event) =>
-          setIsOpen((event.target as HTMLDetailsElement).open)
-        }
-        open={isOpen}
         {...rest}
+        open={isOpen}
+        onToggle={(event) => setIsOpen(event.currentTarget.open)}
       >
🤖 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 `@apps/example/src/components/docs-mdx/file-tree.tsx` around lines 97 - 119,
FolderProps currently extends DetailsHTMLAttributes which allows callers to
override the internal open/onToggle behavior; change the prop type to
Omit<DetailsHTMLAttributes<HTMLDetailsElement>, "open" | "onToggle"> so incoming
props can't override the component-controlled open and onToggle, and in the
Folder component replace the event.target cast with event.currentTarget to avoid
unsafe type assertions (update the onToggle handler inside Folder to use
event.currentTarget and setIsOpen based on its .open). Ensure the {...rest}
spread stays but now cannot contain open/onToggle.

>
<summary data-leadtype-file-tree-summary="">
<ChevronIcon />
<FolderIcon />
<span data-leadtype-file-tree-name="">{name}/</span>
</summary>
<ul data-leadtype-file-tree-list="">{children}</ul>
</details>
</li>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
);
}

export type FileProps = LiHTMLAttributes<HTMLLIElement> & {
name: string;
};

export function File({ name, ...rest }: FileProps) {
return (
<li data-leadtype-file-tree-file="" {...rest}>
<FileIcon />
<span data-leadtype-file-tree-name="">{name}</span>
</li>
);
}
14 changes: 14 additions & 0 deletions apps/example/src/components/docs-mdx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ export {
type AccordionItemProps,
type AccordionProps,
} from "./accordion";
export {
Audience,
type AudienceProps,
type AudienceTarget,
} from "./audience";
export {
Callout,
type CalloutProps,
Expand All @@ -18,11 +23,20 @@ export {
type PackageManager,
} from "./command-tabs";
export { Example, type ExampleProps, type ExampleSourceFile } from "./example";
export {
File,
type FileProps,
FileTree,
type FileTreeProps,
Folder,
type FolderProps,
} from "./file-tree";
export {
type MdxComponents,
mdxComponents,
} from "./mdx-components";
export { Mermaid, type MermaidProps } from "./mermaid";
export { Prompt, type PromptProps } from "./prompt";
export {
Selector,
type SelectorOption,
Expand Down
8 changes: 8 additions & 0 deletions apps/example/src/components/docs-mdx/mdx-components.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Accordion, AccordionItem } from "./accordion";
import { Audience } from "./audience";
import { Callout } from "./callout";
import { Card, Cards } from "./card";
import { CommandTabs } from "./command-tabs";
import { Example } from "./example";
import { File, FileTree, Folder } from "./file-tree";
import { Mermaid } from "./mermaid";
import { Prompt } from "./prompt";
import { Selector } from "./selector";
import { Step, Steps } from "./steps";
import { Tab, Tabs } from "./tabs";
Expand All @@ -13,12 +16,17 @@ import { ExtractedTypeTable, TypeTable } from "./type-table";
export const mdxComponents = {
Accordion,
AccordionItem,
Audience,
ExtractedTypeTable,
Callout,
Card,
Cards,
Example,
File,
FileTree,
Folder,
Mermaid,
Prompt,
CommandTabs,
Selector,
Step,
Expand Down
Loading
Loading