Welcome back!
In the previous chapter, Event Selection Mode, we built the "Department Store Directory." We allowed the user to choose When a script runs (e.g., "Before a Tool is Used").
Now, we need to let them choose What tool triggers the script. This is the Matcher Selection Mode.
Let's stick with our department store analogy. You have already entered the "Men's Clothing" department (The Event). Now, you are looking for a specific brand.
You don't want to browse through a pile of 1,000 random shirts. You want to see signs for the brands:
In our specific context:
PreToolUse (Before a tool runs)git (Only run when the user types git)
The Problem: The user has selected "PreToolUse". They have 20 different scripts. 5 of them are for git, 5 are for npm, and 10 run for every command. Showing a flat list of 20 items is confusing.
The Solution: We group these scripts by their "Matcher" (the tool name). The user sees a clean list of tool names. They select git, and then we show them the 5 specific scripts.
Here is how the user interacts with this specific view:
To understand this component, we need to look at three things:
ls, cat, git). If a hook applies to everything, the matcher might be empty or labelled (all).
Let's explore SelectMatcherMode.tsx.
The component receives a list of matchers and a big object containing all the hooks. We need to combine these to figure out how many hooks exist for each matcher.
We use React.useMemo to do this calculation only when data changes, so the menu stays snappy.
const matchersWithSources = React.useMemo(() => {
return matchersForSelectedEvent.map(matcher => {
// 1. Get all hooks for this specific tool (e.g., 'git')
const hooks = hooksByEventAndMatcher[selectedEvent]?.[matcher] || [];
// 2. Return the data we need for the UI
return {
matcher, // e.g., "git"
hookCount: hooks.length, // e.g., 5
};
});
}, [matchersForSelectedEvent, hooksByEventAndMatcher]);
Explanation:
hooks.length).
Just like in the previous chapter, we need to format this data for our <Select /> component. We want the label to look informative, for example: [Global] git.
const options = matchersWithSources.map(item => {
// 1. Create a display label (e.g., "(all)" or "git")
const matcherLabel = item.matcher || '(all)';
return {
// 2. Combine source info and label
label: `[${sourceText}] ${matcherLabel}`,
// 3. The value we pass back when selected
value: item.matcher,
// 4. Helpful description
description: `${item.hookCount} hooks`
};
});
Explanation:
label: This is what the user clicks on. It combines the source (where the config lives) and the tool name.value: This is the raw ID (the tool name) we will send to the parent.description: Tells the user how many items are inside this folder.What if the user clicks "PreToolUse," but there are actually no hooks configured for it? We shouldn't show an empty list; we should tell them what's going on.
if (matchersForSelectedEvent.length === 0) {
return (
<Dialog title={`${selectedEvent} - Matchers`} onCancel={onCancel}>
<Box flexDirection="column" gap={1}>
<Text dimColor>No hooks configured for this event.</Text>
<Text dimColor>To add hooks, edit settings.json.</Text>
</Box>
</Dialog>
);
}
Explanation:
If we have data, we render the interactive list.
return (
<Dialog
title={`${selectedEvent} - Matchers`}
onCancel={onCancel}
>
<Box flexDirection="column">
<Select
options={options}
onChange={(value) => onSelect(value)} // Pass "git" to parent
onCancel={onCancel}
/>
</Box>
</Dialog>
);
Explanation:
title: Updates dynamically, e.g., "PreToolUse - Matchers".onChange: When the user presses Enter on "git", this function fires. It tells the Hooks Config Menu to move to the next state.In this chapter, we built the Matcher Selection Mode.
The Journey So Far:
What's Next? Now that the user has selected "git", they want to see the actual list of scripts that run before git commands. It is finally time to show the individual items.
Let's move on to Chapter 4: Hook Selection Mode.
Generated by Code IQ