From 6b4dae330c8b3f837d7a2829ec1db5c452059b66 Mon Sep 17 00:00:00 2001 From: sck_0 Date: Fri, 27 Feb 2026 09:05:13 +0100 Subject: [PATCH] feat: enhance web app with fuzzy search, syntax highlighting, and pagination - Expand README with detailed Web App section (English) - Improve SEO meta tags in index.html - Add rehype-highlight for code syntax highlighting in skill details - Implement fuzzy search with scoring (name > category > description) - Add clear search button and result counter - Implement Load More pagination (24 items initially) for 950+ skills - Add rehype-highlight and highlight.js dependencies Made-with: Cursor --- README.md | 51 ++++++++++++---- web-app/index.html | 9 ++- web-app/package-lock.json | 97 +++++++++++++++++++++++++++---- web-app/package.json | 2 + web-app/src/pages/Home.jsx | 76 +++++++++++++++++++++--- web-app/src/pages/SkillDetail.jsx | 6 +- 6 files changed, 208 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 4e8598f4..419c31f2 100644 --- a/README.md +++ b/README.md @@ -351,21 +351,52 @@ We have moved the full skill registry to a dedicated catalog to keep this README ### 🌐 Interactive Skills Web App -You can now easily search, filter, and discover the perfect skills for your agent using our local Web App. +A modern web interface to explore, search, and use the 950+ skills directly from your browser. -To launch the app: +#### ✨ Features -1. Double-click the `START_APP.bat` file in the root directory (Windows) or run it from your terminal. -2. The app will automatically configure everything and open in your default browser. +- πŸ” **Full-text search** – Search skills by name, description, or content +- 🏷️ **Category filters** – Frontend, Backend, Security, DevOps, etc. +- πŸ“ **Markdown rendering** – View complete documentation with syntax highlighting +- πŸ“‹ **Copy buttons** – Copy `@skill-name` or full content in 1 click +- πŸ› οΈ **Prompt Builder** – Add custom context before copying +- πŸŒ™ **Dark mode** – Adaptive light/dark interface +- ⚑ **Auto-update** – Automatically syncs with upstream repo -#### πŸ› οΈ New: Interactive Prompt Builder +#### πŸš€ Quick Start -The web app is no longer just a static catalog! When you click on any skill, you will see an **Interactive Prompt Builder** box. -Instead of manually copying `@skill-name` and writing your requirements separately in your IDE: +**Windows:** +```bash +# Double-click or terminal +START_APP.bat +``` -1. Type your specific project constraints into the text box (e.g., "Use React 19 and Tailwind"). -2. Click **Copy Prompt**. -3. Your clipboard now has a fully formatted, ready-to-run prompt combining the skill invocation and your custom context! +**macOS/Linux:** +```bash +# 1. Install dependencies (first time) +cd web-app && npm install + +# 2. Setup assets and launch +npm run app:dev +``` + +**Available npm commands:** +```bash +npm run app:setup # Copy skills to web-app/public/ +npm run app:dev # Start dev server +npm run app:build # Production build +npm run app:preview # Preview production build +``` + +The app automatically opens at `http://localhost:5173` (or alternative port). + +#### πŸ› οΈ Prompt Builder + +On each skill page you'll find the **Prompt Builder**: +1. Write specific requirements (e.g., "Use React 19, TypeScript and Tailwind") +2. Click **Copy Prompt** – copies `@skill-name + context` +3. Or **Copy Full Content** – copies the full documentation +4. Paste into your AI assistant (Claude, Cursor, Gemini, etc.) πŸ‘‰ **[View the Complete Skill Catalog (CATALOG.md)](CATALOG.md)** diff --git a/web-app/index.html b/web-app/index.html index 7645cc8c..a04b9def 100644 --- a/web-app/index.html +++ b/web-app/index.html @@ -4,7 +4,14 @@ - web-app + + + + + + + + Antigravity Skills | 950+ AI Agentic Skills Catalog
diff --git a/web-app/package-lock.json b/web-app/package-lock.json index b20f3a63..d49b8f9d 100644 --- a/web-app/package-lock.json +++ b/web-app/package-lock.json @@ -11,11 +11,13 @@ "clsx": "^2.1.1", "framer-motion": "^12.34.2", "github-markdown-css": "^5.9.0", + "highlight.js": "^11.11.1", "lucide-react": "^0.574.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-markdown": "^10.1.0", "react-router-dom": "^7.13.0", + "rehype-highlight": "^7.0.2", "tailwind-merge": "^3.5.0" }, "devDependencies": { @@ -78,7 +80,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1764,7 +1765,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1818,7 +1818,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1971,7 +1970,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2332,7 +2330,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2738,6 +2735,19 @@ "node": ">=8" } }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", @@ -2765,6 +2775,22 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-whitespace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", @@ -2795,6 +2821,15 @@ "hermes-estree": "0.25.1" } }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/html-url-attributes": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", @@ -3329,6 +3364,21 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/lowlight": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz", + "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "highlight.js": "~11.11.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4141,7 +4191,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -4169,7 +4218,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -4221,7 +4269,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -4231,7 +4278,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -4314,6 +4360,23 @@ "react-dom": ">=18" } }, + "node_modules/rehype-highlight": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/rehype-highlight/-/rehype-highlight-7.0.2.tgz", + "integrity": "sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-text": "^4.0.0", + "lowlight": "^3.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-parse": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", @@ -4631,6 +4694,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-is": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", @@ -4774,7 +4851,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -4896,7 +4972,6 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/web-app/package.json b/web-app/package.json index efa74166..0d93533b 100644 --- a/web-app/package.json +++ b/web-app/package.json @@ -13,11 +13,13 @@ "clsx": "^2.1.1", "framer-motion": "^12.34.2", "github-markdown-css": "^5.9.0", + "highlight.js": "^11.11.1", "lucide-react": "^0.574.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-markdown": "^10.1.0", "react-router-dom": "^7.13.0", + "rehype-highlight": "^7.0.2", "tailwind-merge": "^3.5.0" }, "devDependencies": { diff --git a/web-app/src/pages/Home.jsx b/web-app/src/pages/Home.jsx index 7cdd2ee7..85bbd0f0 100644 --- a/web-app/src/pages/Home.jsx +++ b/web-app/src/pages/Home.jsx @@ -10,6 +10,7 @@ export function Home() { const [search, setSearch] = useState(''); const [categoryFilter, setCategoryFilter] = useState('all'); const [loading, setLoading] = useState(true); + const [displayCount, setDisplayCount] = useState(24); // Show 24 initially (6 rows of 4) useEffect(() => { fetch('/skills.json') @@ -25,15 +26,39 @@ export function Home() { }); }, []); + // Fuzzy search with scoring + const calculateScore = (skill, terms) => { + let score = 0; + const nameLower = skill.name.toLowerCase(); + const descLower = (skill.description || '').toLowerCase(); + const catLower = (skill.category || '').toLowerCase(); + + for (const term of terms) { + // Exact name match (highest priority) + if (nameLower === term) score += 100; + // Name starts with term + else if (nameLower.startsWith(term)) score += 50; + // Name contains term + else if (nameLower.includes(term)) score += 30; + // Category match + else if (catLower.includes(term)) score += 20; + // Description contains term + else if (descLower.includes(term)) score += 10; + } + return score; + }; + useEffect(() => { let result = skills; if (search) { - const lowerSearch = search.toLowerCase(); - result = result.filter(skill => - skill.name.toLowerCase().includes(lowerSearch) || - skill.description.toLowerCase().includes(lowerSearch) - ); + const terms = search.toLowerCase().trim().split(/\s+/).filter(t => t.length > 0); + if (terms.length > 0) { + result = result + .map(skill => ({ ...skill, _score: calculateScore(skill, terms) })) + .filter(skill => skill._score > 0) + .sort((a, b) => b._score - a._score); + } } if (categoryFilter !== 'all') { @@ -43,6 +68,11 @@ export function Home() { setFilteredSkills(result); }, [search, categoryFilter, skills]); + // Reset display count when search/filter changes + useEffect(() => { + setDisplayCount(24); + }, [search, categoryFilter]); + const categories = ['all', ...new Set(skills.map(s => s.category).filter(Boolean))]; return ( @@ -50,7 +80,11 @@ export function Home() {

Explore Skills

-

Discover {skills.length} agentic capabilities for your AI assistant.

+

+ {search || categoryFilter !== 'all' + ? `Showing ${filteredSkills.length} of ${skills.length} skills` + : `Discover ${skills.length} agentic capabilities for your AI assistant.`} +

@@ -59,11 +93,20 @@ export function Home() { setSearch(e.target.value)} /> + {search && ( + + )}
@@ -93,7 +136,7 @@ export function Home() {

Try adjusting your search or filter.

) : ( - filteredSkills.map((skill) => ( + filteredSkills.slice(0, displayCount).map((skill) => ( + + {/* Load More Button */} + {!loading && filteredSkills.length > displayCount && ( +
+ +
+ )} ); diff --git a/web-app/src/pages/SkillDetail.jsx b/web-app/src/pages/SkillDetail.jsx index ddc448db..8e5f5330 100644 --- a/web-app/src/pages/SkillDetail.jsx +++ b/web-app/src/pages/SkillDetail.jsx @@ -2,7 +2,9 @@ import { useState, useEffect } from 'react'; import { useParams, Link } from 'react-router-dom'; import Markdown from 'react-markdown'; +import rehypeHighlight from 'rehype-highlight'; import { ArrowLeft, Copy, Check, FileCode, AlertTriangle } from 'lucide-react'; +import 'highlight.js/styles/github-dark.css'; export function SkillDetail() { const { id } = useParams(); @@ -157,8 +159,8 @@ export function SkillDetail() {
-
- {content} +
+ {content}