Skip to content

Commit 5c32372

Browse files
committed
feat: add search and filter functionality to documentation UI
1 parent 6b79656 commit 5c32372

File tree

7 files changed

+518
-13
lines changed

7 files changed

+518
-13
lines changed

CHANGELOG.md

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,31 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
66
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.7.0] - 2026-03-15
9+
10+
### Added
11+
12+
- **Search & Filter System** - Find endpoints quickly with powerful filtering capabilities:
13+
- **Real-time Search** - Instantly filter endpoints by name, path, description, or tags as you
14+
type (300ms debounce)
15+
- **Type Filter** - Filter by procedure type (All Types / Queries / Mutations)
16+
- **Auth Filter** - Show only public or protected endpoints
17+
- **Tag Filter** - Filter by custom tags from route metadata
18+
- **Active Filter Highlighting** - Selected filters are highlighted in blue for clear visual
19+
feedback
20+
- **Filter Persistence** - All filter states are automatically saved to localStorage and restored
21+
on page reload
22+
- **Clear Filters Button** - One-click reset with red highlighting when filters are active
23+
- **Dynamic Results Counter** - Shows "Showing X of Y endpoints" based on active filters
24+
- **Smart UI Updates** - Automatically hides empty route groups and sidebar sections when filtered
25+
26+
---
27+
828
## [0.6.1] - 2026-02-26
929

1030
### Fixed
1131

12-
- **`meta.name` not rendered in UI** The `name` field from route metadata was never displayed in
32+
- **`meta.name` not rendered in UI** - The `name` field from route metadata was never displayed in
1333
the generated documentation. It now appears as a title in the route card header, with the path
1434
shown beneath it as a secondary label. The sidebar also uses the name when available, falling back
1535
to the path segment.
@@ -20,12 +40,12 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
2040

2141
### Breaking Changes
2242

23-
- **`RouteMeta` type restructured** The `docs` metadata fields (`description`, `tags`,
43+
- **`RouteMeta` type restructured** - The `docs` metadata fields (`description`, `tags`,
2444
`deprecated`, `auth`, `roles`) must now be nested under a `docs` sub-object. The previous flat
2545
shape was inconsistent with how the HTML generator read the metadata, meaning those fields were
2646
silently ignored in the generated documentation.
2747

28-
**Before (broken fields were silently ignored):**
48+
**Before (broken - fields were silently ignored):**
2949

3050
```ts
3151
t.procedure.meta({

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ router with zero configuration. Not only does it document your API, but it also
4141

4242
- ⚡️ **Zero Config** - Works out of the box with any tRPC v11 router
4343
- 🧪 **Interactive Testing** - **Test endpoints directly in the browser with live fetch requests**
44+
- 🔍 **Search & Filter** - Instantly find endpoints with real-time search and smart filters
4445
- 🎨 **Beautiful UI** - Modern, responsive design with smooth animations
45-
- 🔍 **Smart Schema Inference** - Automatically extracts types from Zod validators
46+
- 🧠 **Smart Schema Inference** - Automatically extracts types from Zod validators
4647
- 📝 **Auto-Filled Examples** - Pre-filled JSON with required fields, click to add optional fields
4748
- 🔐 **Header Management** - Add custom headers (auth tokens, etc.), save and reuse them
4849
- 🌐 **Deploy Anywhere** - Works with Express, Next.js, Cloudflare Workers, and more
@@ -93,6 +94,17 @@ Powered by Zod's `toJSONSchema()` method, the generator automatically:
9394
- **Date fields** - `z.date()` and `z.coerce.date()` are represented as ISO 8601 strings
9495
(`{ type: "string", format: "date-time" }`), matching how tRPC transports dates over the wire
9596

97+
### 🔍 Search & Filter System
98+
99+
Quickly find what you need in large APIs:
100+
101+
- **Real-time Search** - Filter endpoints by name, path, description, or tags as you type
102+
- **Smart Filters** - Filter by type (query/mutation), auth status (public/protected), or custom
103+
tags
104+
- **Filter Persistence** - Your filter selections are saved across page reloads
105+
- **Visual Feedback** - Active filters highlighted in blue, clear button turns red when active
106+
- **Dynamic Results** - Shows count of visible endpoints and auto-hides empty sections
107+
96108
### 🎨 Modern, Beautiful UI
97109

98110
- **Responsive Design** - Perfect on desktop, tablet, and mobile

bun.lock

Lines changed: 7 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "trpc-docs-generator",
3-
"version": "0.6.1",
3+
"version": "0.7.0",
44
"author": "Lior Cohen",
55
"homepage": "https://github.com/liorcodev/trpc-docs-generator#readme",
66
"repository": {
@@ -14,9 +14,9 @@
1414
"main": "./dist/index.js",
1515
"module": "./dist/index.js",
1616
"devDependencies": {
17-
"@trpc/server": "^11.12.0",
17+
"@trpc/server": "^11.12.1",
1818
"@types/bun": "^1.3.10",
19-
"@types/node": "^25.3.5",
19+
"@types/node": "^25.5.0",
2020
"prettier": "^3.8.1",
2121
"sharp": "^0.34.5",
2222
"typescript": "^5.9.3",

src/generate-html.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,53 @@ export function generateDocsHtml(routes: RouteInfo[], options: DocsGeneratorOpti
122122
</div>
123123
</div>
124124
</div>
125+
126+
<div class="search-filter-bar">
127+
<div class="search-box">
128+
<span class="iconify search-icon" data-icon="mdi:magnify"></span>
129+
<input
130+
type="search"
131+
id="searchInput"
132+
class="search-input"
133+
placeholder="Search endpoints, descriptions..."
134+
/>
135+
</div>
136+
137+
<div class="filter-group">
138+
<select id="typeFilter" class="filter-select">
139+
<option value="all">All Types</option>
140+
<option value="query">Queries</option>
141+
<option value="mutation">Mutations</option>
142+
</select>
143+
144+
<select id="authFilter" class="filter-select">
145+
<option value="all">All Endpoints</option>
146+
<option value="public">Public</option>
147+
<option value="protected">Protected</option>
148+
</select>
149+
150+
<select id="tagFilter" class="filter-select">
151+
<option value="all">All Tags</option>
152+
${Array.from(
153+
new Set(routes.flatMap(r => (r.meta?.docs as RouteMeta['docs'])?.tags || []))
154+
)
155+
.sort()
156+
.map(tag => `<option value="${escapeHtml(tag)}">${escapeHtml(tag)}</option>`)
157+
.join('')}
158+
</select>
159+
</div>
160+
161+
<div class="filter-controls">
162+
<button class="clear-filters-btn" id="clearFiltersBtn" onclick="clearFilters()">
163+
<span class="iconify" data-icon="mdi:filter-remove" style="width: 14px; height: 14px;"></span>
164+
Clear Filters
165+
</button>
166+
</div>
167+
168+
<div class="results-count" id="resultsCount">
169+
Showing ${routes.length} of ${routes.length} endpoints
170+
</div>
171+
</div>
125172
</header>
126173
127174
${Object.entries(groupedRoutes)
@@ -182,8 +229,23 @@ function generateRouteCard(route: RouteInfo): string {
182229
const docs = route.meta?.docs as RouteMeta['docs'];
183230
const routeId = route.path.replace(/\./g, '-');
184231

232+
// Prepare searchable text and filter attributes
233+
const searchableText = [
234+
route.path,
235+
route.meta?.name || '',
236+
docs?.description || '',
237+
...(docs?.tags || [])
238+
]
239+
.join(' ')
240+
.toLowerCase();
241+
185242
return `
186-
<div class="route-card" id="${routeId}">
243+
<div class="route-card"
244+
id="${routeId}"
245+
data-type="${route.type}"
246+
data-auth="${docs?.auth ? 'true' : 'false'}"
247+
data-tags="${(docs?.tags || []).join(',')}"
248+
data-search="${escapeHtml(searchableText)}">
187249
<div class="route-header">
188250
<span class="method-badge method-${route.type}">${route.type}</span>
189251
<div class="route-path-info">

0 commit comments

Comments
 (0)