feat: toggleable deep search with configurable scope

Add a deep search toggle button in the search bar that controls whether
search includes description and/or file content beyond just the name.

- Default off (matching upstream name-only search behavior)
- Settings: "Deep search by default" toggle + "Deep search scope" dropdown
  (description only / file content only / both)
- Toggle button sits alongside existing filter and sort buttons

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trevor Hart 2026-03-30 18:37:31 -04:00 committed by Railly
parent 4d433a23c6
commit 28daafa3df
8 changed files with 2789 additions and 6 deletions

File diff suppressed because one or more lines are too long

2667
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -35,6 +35,8 @@ export default class AgentfilesPlugin extends Plugin {
this.addSettingTab(new AgentfilesSettingTab(this.app, this));
this.refreshStore();
this.store.setDeepSearch(this.settings.deepSearchDefault ?? false);
this.store.setDeepSearchScope(this.settings.deepSearchScope ?? "both");
this.startWatcher();
}

View file

@ -59,6 +59,37 @@ export class AgentfilesSettingTab extends PluginSettingTab {
})
);
new Setting(containerEl)
.setName("Deep search by default")
.setDesc(
"Enable deep search when the view opens (can always be toggled in the search bar)"
)
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.deepSearchDefault ?? false)
.onChange(async (value) => {
this.plugin.settings.deepSearchDefault = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName("Deep search scope")
.setDesc("What to include when deep search is enabled")
.addDropdown((drop) =>
drop
.addOptions({
both: "Description and file content",
description: "Description only",
content: "File content only",
})
.setValue(this.plugin.settings.deepSearchScope ?? "both")
.onChange(async (value) => {
this.plugin.settings.deepSearchScope = value as "description" | "content" | "both";
await this.plugin.saveSettings();
})
);
new Setting(containerEl).setName("Marketplace").setHeading();
new Setting(containerEl)

View file

@ -1,5 +1,5 @@
import { Events } from "obsidian";
import type { SkillItem, SidebarFilter, ChopsSettings } from "./types";
import type { SkillItem, SidebarFilter, DeepSearchScope, ChopsSettings } from "./types";
import { scanAll, getProjectName } from "./scanner";
import { getSkillkitStatsWithDaily, getSkillConflicts, getSkillWarnings, isSkillkitAvailable } from "./skillkit";
@ -7,6 +7,8 @@ export class SkillStore extends Events {
private items: Map<string, SkillItem> = new Map();
private _filter: SidebarFilter = { kind: "all" };
private _searchQuery = "";
private _deepSearch = false;
private _deepSearchScope: DeepSearchScope = "both";
private _projectsHomeDir = "";
get filter(): SidebarFilter {
@ -17,6 +19,10 @@ export class SkillStore extends Events {
return this._searchQuery;
}
get deepSearch(): boolean {
return this._deepSearch;
}
get allItems(): SkillItem[] {
return Array.from(this.items.values());
}
@ -50,9 +56,13 @@ export class SkillStore extends Events {
if (this._searchQuery) {
const q = this._searchQuery.toLowerCase();
const searchDesc = this._deepSearch && (this._deepSearchScope === "description" || this._deepSearchScope === "both");
const searchContent = this._deepSearch && (this._deepSearchScope === "content" || this._deepSearchScope === "both");
result = result.filter(
(i) =>
i.name.toLowerCase().includes(q)
i.name.toLowerCase().includes(q) ||
(searchDesc && i.description.toLowerCase().includes(q)) ||
(searchContent && i.content.toLowerCase().includes(q))
);
}
@ -126,6 +136,18 @@ export class SkillStore extends Events {
this.trigger("updated");
}
setDeepSearch(enabled: boolean): void {
if (this._deepSearch === enabled) return;
this._deepSearch = enabled;
this.trigger("updated");
}
setDeepSearchScope(scope: DeepSearchScope): void {
if (this._deepSearchScope === scope) return;
this._deepSearchScope = scope;
this.trigger("updated");
}
toggleFavorite(id: string, settings: ChopsSettings): void {
const item = this.items.get(id);
if (!item) return;

View file

@ -68,10 +68,14 @@ export type SidebarFilter =
export type NamingMode = "auto" | "filename";
export type DeepSearchScope = "description" | "content" | "both";
export interface ChopsSettings {
tools: Record<string, { enabled: boolean; customPaths: string[] }>;
watchEnabled: boolean;
watchDebounceMs: number;
deepSearchDefault: boolean;
deepSearchScope: DeepSearchScope;
favorites: string[];
collections: Record<string, string[]>;
customScanPaths: string[];
@ -85,6 +89,8 @@ export const DEFAULT_SETTINGS: ChopsSettings = {
tools: {},
watchEnabled: true,
watchDebounceMs: 500,
deepSearchDefault: false,
deepSearchScope: "both",
favorites: [],
collections: {},
customScanPaths: [],

View file

@ -10,6 +10,7 @@ export class ListPanel {
private onSelect: (item: SkillItem) => void;
private selectedId: string | null = null;
private inputEl: HTMLInputElement | null = null;
private deepToggleEl: HTMLElement | null = null;
private listEl: HTMLElement | null = null;
private typeFilter: string | null = null;
private sortBy: "name" | "usage" = "name";
@ -44,6 +45,14 @@ export class ListPanel {
this.store.setSearch(this.inputEl!.value);
});
this.deepToggleEl = searchContainer.createDiv("as-deep-toggle");
setIcon(this.deepToggleEl, "search-code");
this.deepToggleEl.setAttribute("aria-label", "Toggle deep search");
this.deepToggleEl.addEventListener("click", () => {
this.store.setDeepSearch(!this.store.deepSearch);
this.updateDeepToggle();
});
const filterBtn = searchContainer.createDiv("as-filter-btn");
setIcon(filterBtn, "filter");
filterBtn.setAttribute("aria-label", "Filter by status");
@ -66,9 +75,21 @@ export class ListPanel {
}
this.inputEl.value = this.store.searchQuery;
this.updateDeepToggle();
this.renderList();
}
private updateDeepToggle(): void {
if (!this.deepToggleEl) return;
this.deepToggleEl.toggleClass("is-active", this.store.deepSearch);
this.deepToggleEl.setAttribute(
"aria-label",
this.store.deepSearch
? "Deep search enabled — click to search names only"
: "Deep search disabled — click to search file content"
);
}
private toggleDropdown(anchor: HTMLElement): void {
if (this.dropdownEl) {
this.dropdownEl.remove();

View file

@ -132,12 +132,16 @@
/* Search */
.as-search {
display: flex;
align-items: center;
gap: 4px;
padding: 8px;
border-bottom: 1px solid var(--background-modifier-border);
}
.as-search-input {
width: 100%;
flex: 1;
min-width: 0;
padding: 6px 10px;
border: 1px solid var(--background-modifier-border);
border-radius: var(--radius-s);
@ -152,6 +156,36 @@
box-shadow: 0 0 0 2px color-mix(in srgb, var(--interactive-accent) 25%, transparent);
}
.as-deep-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
flex-shrink: 0;
border: 1px solid var(--background-modifier-border);
border-radius: var(--radius-s);
background: var(--background-primary);
color: var(--text-faint);
cursor: pointer;
}
.as-deep-toggle:hover {
color: var(--text-muted);
border-color: var(--text-faint);
}
.as-deep-toggle.is-active {
background: var(--interactive-accent);
color: var(--text-on-accent);
border-color: var(--interactive-accent);
}
.as-deep-toggle svg {
width: 14px;
height: 14px;
}
/* List */
.as-list-items {
padding: 4px;