Skip to content
Draft
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
12 changes: 10 additions & 2 deletions packages/react-grab/src/core/agent/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
AgentSession,
AgentOptions,
OverlayBounds,
SettableOptions,
} from "../../types.js";
import {
createSession,
Expand Down Expand Up @@ -68,6 +69,7 @@ export interface AgentManager {

export const createAgentManager = (
initialAgentOptions: AgentOptions | undefined,
getPluginOptions?: () => SettableOptions | undefined,
): AgentManager => {
const [sessions, setSessions] = createSignal<Map<string, AgentSession>>(
new Map(),
Expand Down Expand Up @@ -339,7 +341,10 @@ export const createAgentManager = (

const content = existingSession
? existingSession.context.content
: await generateSnippet(elements, { maxLines: Infinity });
: await generateSnippet(elements, {
maxLines: Infinity,
ignoreComponents: getPluginOptions?.()?.ignoreComponents,
});

const context: AgentContext = {
content,
Expand Down Expand Up @@ -367,7 +372,10 @@ export const createAgentManager = (
const componentName =
elements.length > 1
? undefined
: (await getNearestComponentName(firstElement)) || undefined;
: (await getNearestComponentName(
firstElement,
getPluginOptions?.()?.ignoreComponents,
)) || undefined;

session = createSession(
context,
Expand Down
68 changes: 58 additions & 10 deletions packages/react-grab/src/core/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
traverseFiber,
} from "bippy";
import { MAX_HTML_FALLBACK_LENGTH } from "../constants.js";
import { IgnoreComponentsOption } from "../types.js";

const NEXT_INTERNAL_COMPONENT_NAMES = new Set([
"InnerLayoutRouter",
Expand Down Expand Up @@ -65,12 +66,36 @@ export const checkIsInternalComponentName = (name: string): boolean => {
return false;
};

export const checkIsSourceComponentName = (name: string): boolean => {
const checkIsIgnored = (
name: string,
ignoreComponents?: IgnoreComponentsOption,
): boolean => {
if (!ignoreComponents) return false;
if (typeof ignoreComponents === "function") {
return ignoreComponents(name);
}
for (const pattern of ignoreComponents) {
if (typeof pattern === "string") {
if (name === pattern) return true;
} else if (pattern.test(name)) {
return true;
}
}
return false;
};

export const checkIsSourceComponentName = (
name: string,
ignoreComponents?: IgnoreComponentsOption,
): boolean => {
if (name.length <= 1) return false;
if (checkIsInternalComponentName(name)) return false;
if (!isCapitalized(name)) return false;
if (name.startsWith("Primitive.")) return false;
if (name.includes("Provider") && name.includes("Context")) return false;

if (checkIsIgnored(name, ignoreComponents)) return false;

if (name.includes("Provider") || name.includes("Context")) return false;
return true;
};

Expand All @@ -90,29 +115,40 @@ export const getStack = async (

export const getNearestComponentName = async (
element: Element,
ignoreComponents?: IgnoreComponentsOption,
): Promise<string | null> => {
if (!isInstrumentationActive()) return null;
const stack = await getStack(element);
if (!stack) return null;

for (const frame of stack) {
if (frame.functionName && checkIsSourceComponentName(frame.functionName)) {
if (
frame.functionName &&
checkIsSourceComponentName(frame.functionName, ignoreComponents)
) {
return frame.functionName;
}
}

return null;
};

const isUsefulComponentName = (name: string): boolean => {
const isUsefulComponentName = (
name: string,
ignoreComponents?: IgnoreComponentsOption,
): boolean => {
if (!name) return false;
if (checkIsInternalComponentName(name)) return false;
if (name.startsWith("Primitive.")) return false;
if (name === "SlotClone" || name === "Slot") return false;
if (checkIsIgnored(name, ignoreComponents)) return false;
return true;
};

export const getComponentDisplayName = (element: Element): string | null => {
export const getComponentDisplayName = (
element: Element,
ignoreComponents?: IgnoreComponentsOption,
): string | null => {
if (!isInstrumentationActive()) return null;
const fiber = getFiberFromHostInstance(element);
if (!fiber) return null;
Expand All @@ -121,7 +157,7 @@ export const getComponentDisplayName = (element: Element): string | null => {
while (currentFiber) {
if (isCompositeFiber(currentFiber)) {
const name = getDisplayName(currentFiber.type);
if (name && isUsefulComponentName(name)) {
if (name && isUsefulComponentName(name, ignoreComponents)) {
return name;
}
}
Expand All @@ -133,6 +169,7 @@ export const getComponentDisplayName = (element: Element): string | null => {

interface GetElementContextOptions {
maxLines?: number;
ignoreComponents?: IgnoreComponentsOption;
}

const hasSourceFiles = (stack: StackFrame[] | null): boolean => {
Expand All @@ -146,6 +183,7 @@ const hasSourceFiles = (stack: StackFrame[] | null): boolean => {
const getComponentNamesFromFiber = (
element: Element,
maxCount: number,
ignoreComponents?: IgnoreComponentsOption,
): string[] => {
if (!isInstrumentationActive()) return [];
const fiber = getFiberFromHostInstance(element);
Expand All @@ -158,7 +196,7 @@ const getComponentNamesFromFiber = (
if (componentNames.length >= maxCount) return true;
if (isCompositeFiber(currentFiber)) {
const name = getDisplayName(currentFiber.type);
if (name && isUsefulComponentName(name)) {
if (name && isUsefulComponentName(name, ignoreComponents)) {
componentNames.push(name);
}
}
Expand Down Expand Up @@ -196,7 +234,10 @@ export const getElementContext = async (
if (
frame.isServer &&
(!frame.functionName ||
checkIsSourceComponentName(frame.functionName))
checkIsSourceComponentName(
frame.functionName,
options.ignoreComponents,
))
) {
stackContext.push(
`\n in ${frame.functionName || "<anonymous>"} (at Server)`,
Expand All @@ -207,7 +248,10 @@ export const getElementContext = async (
let line = "\n in ";
const hasComponentName =
frame.functionName &&
checkIsSourceComponentName(frame.functionName);
checkIsSourceComponentName(
frame.functionName,
options.ignoreComponents,
);

if (hasComponentName) {
line += `${frame.functionName} (at `;
Expand All @@ -232,7 +276,11 @@ export const getElementContext = async (
return `${html}${stackContext.join("")}`;
}

const componentNames = getComponentNamesFromFiber(element, maxLines);
const componentNames = getComponentNamesFromFiber(
element,
maxLines,
options.ignoreComponents,
);
if (componentNames.length > 0) {
const componentContext = componentNames
.map((name) => `\n in ${name}`)
Expand Down
3 changes: 3 additions & 0 deletions packages/react-grab/src/core/copy.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { copyContent } from "../utils/copy-content.js";
import { generateSnippet } from "../utils/generate-snippet.js";
import { IgnoreComponentsOption } from "../types.js";

interface CopyOptions {
maxContextLines?: number;
getContent?: (elements: Element[]) => Promise<string> | string;
ignoreComponents?: IgnoreComponentsOption;
}

interface CopyHooks {
Expand Down Expand Up @@ -37,6 +39,7 @@ export const tryCopyWithFallback = async (
} else {
const snippets = await generateSnippet(elements, {
maxLines: options.maxContextLines,
ignoreComponents: options.ignoreComponents,
});
const combinedSnippets = snippets.join("\n\n");

Expand Down
Loading