feat: add requested icons + fix Raycast extension#93
Conversation
Remove individual registry/<slug>.json files to stay within Cloudflare Pages 20K file limit. Extension now fetches SVGs directly and caches the registry. Also adds JSX/HTML/DataURI copy formats and updates metadata to 5,600+ icons. Co-Authored-By: Glinr <bot@glincker.com>
There was a problem hiding this comment.
Pull request overview
Adds two requested brand icons and updates the Raycast extension + static API generation to avoid Cloudflare Pages’ 20K file limit by removing per-icon JSON outputs and switching the extension to use a cached registry plus direct SVG fetching.
Changes:
- Add new icons:
jinritoutiaoandformat-json-online(SVGs +icons.jsonentries). - Update
generate-api.tsto output onlyregistry.json+categories.json(no per-icon detail files). - Refactor Raycast extension to use
/api/registry.json, client-side filtering, direct SVG fetch per variant, and add “copy as JSX/HTML/Data URI/Hex” actions.
Reviewed changes
Copilot reviewed 7 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
src/scripts/generate-api.ts |
Stops generating per-icon registry files; expands registry.json fields for extension use |
src/data/icons.json |
Adds two icon entries and normalizes various Unicode-escaped strings |
public/icons/jinritoutiao/default.svg |
Adds Jinritoutiao SVG asset |
public/icons/format-json-online/default.svg |
Adds Format JSON Online SVG asset |
extensions/raycast/src/api.ts |
Switches to cached registry fetching + direct SVG fetch; adds copy-format helpers |
extensions/raycast/src/search-icons.tsx |
Updates search UX, adds “Copy As …” actions, and uses aliases as keywords |
extensions/raycast/README.md |
Updates counts and documents new copy formats |
extensions/raycast/package.json |
Updates extension metadata strings to 5,600+ icon count |
extensions/raycast/CHANGELOG.md |
Documents fix + new features for v1.1.0 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * Note: Individual per-icon detail files are NOT generated to stay within | ||
| * Cloudflare Pages' 20,000 file deployment limit. Extensions should fetch | ||
| * SVG content directly from /icons/{slug}/{variant}.svg. |
There was a problem hiding this comment.
This script no longer generates per-icon detail files; clients still calling the old per-icon endpoints (e.g. extensions/figma/src/main.ts uses /api/registry/{slug} and /api/registry?...) will now 404 unless the site keeps a compatibility route/rewrite. Consider updating those consumers (and API docs) to use /api/registry.json + direct SVG fetching, or add a compatibility endpoint.
| return { | ||
| total: data.total, | ||
| icons: filtered.slice(0, limit), | ||
| }; |
There was a problem hiding this comment.
searchIcons() returns total: data.total (the full registry size) even after applying query/category filters, so SearchResult.total becomes inaccurate for pagination/UI. Consider returning the filtered match count (and optionally include the unfiltered total in a separate field if needed).
| function toPascalCase(str: string): string { | ||
| return str | ||
| .replace(/[^a-zA-Z0-9]+(.)/g, (_, c: string) => c.toUpperCase()) | ||
| .replace(/^(.)/, (_, c: string) => c.toUpperCase()); | ||
| } |
There was a problem hiding this comment.
toPascalCase() can generate an invalid component name when the slug starts with a digit (e.g. "01dotai" => "01dotaiIcon"), making the copied JSX snippet syntactically invalid. Consider ensuring the generated name always starts with a valid identifier character (prefix with "Icon"/"Svg" or "_" when needed).
| function svgToJsxAttrs(svg: string): string { | ||
| return svg | ||
| .replace(/\bclass="/g, 'className="') | ||
| .replace(/\bxmlns="[^"]*"/g, "") | ||
| .replace( |
There was a problem hiding this comment.
The SVG->JSX conversion doesn’t handle style="..." attributes, which React expects as a style object. Since some icons include style attributes (e.g. format-json-online has ), the copied JSX can be invalid/ignored. Consider converting style strings to JSX style objects or using a robust transformer (e.g. SVGR).
Greptile SummaryThis PR adds two new icons (Jinritoutiao and Format JSON Online), fixes the Raycast extension's 404 error by dropping per-icon JSON files (Cloudflare Pages 20K-file limit) in favour of a shared The architecture change is sound — the Confidence Score: 5/5Safe to merge — all findings are P2 style/quality suggestions with no impact on current runtime behaviour. The 404 fix is correctly implemented, both new SVG icons are valid, registry caching works as intended, and the new copy-format helpers are pure functions with straightforward logic. The only concerns are a latent svgToJsxAttrs CSS-in-style-block risk (not triggered by any current icon) and a misleading total field that isn't consumed by the UI today. extensions/raycast/src/api.ts — specifically svgToJsxAttrs and the total field in searchIcons Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant Raycast as Raycast Extension
participant Cache as Module Cache
participant API as thesvg.org/api
participant SVG as thesvg.org/icons
User->>Raycast: Search / open detail
Raycast->>Cache: cachedRegistry?
alt Cache miss
Cache-->>Raycast: null
Raycast->>API: GET /api/registry.json
API-->>Raycast: {total, icons[]}
Raycast->>Cache: store registry
else Cache hit
Cache-->>Raycast: registry
end
Raycast-->>User: show filtered icon list
User->>Raycast: View Details (Cmd+D)
Raycast->>Cache: get entry from registry
loop For each variant
Raycast->>SVG: GET /icons/{slug}/{variant}.svg
SVG-->>Raycast: SVG content
end
Raycast-->>User: IconDetailView with metadata
User->>Raycast: Copy as JSX / HTML / DataURI / Hex
Raycast->>Raycast: toJsx() / toHtmlImg() / toDataUri()
Raycast-->>User: content copied to clipboard
Reviews (1): Last reviewed commit: "fix: resolve Raycast 404 error, remove p..." | Re-trigger Greptile |
| function svgToJsxAttrs(svg: string): string { | ||
| return svg | ||
| .replace(/\bclass="/g, 'className="') | ||
| .replace(/\bxmlns="[^"]*"/g, "") | ||
| .replace( | ||
| /\b([a-z]+)-([a-z])/g, | ||
| (_, a: string, b: string) => `${a}${b.toUpperCase()}`, | ||
| ); | ||
| } |
There was a problem hiding this comment.
svgToJsxAttrs corrupts CSS inside <style> blocks
The kebab-to-camelCase regex runs over the entire SVG string, including any <style> block content. CSS property names inside <style> must stay kebab-case; converting them to camelCase breaks the stylesheet. The current SVGs are safe (only fill: properties), but any future icon whose <style> block contains background-color, stroke-width, font-size, etc. will produce invalid CSS in the JSX output.
Consider scoping the attribute rename to only SVG/HTML tag attribute positions, or at minimum restrict it to run only outside <style>...</style> blocks:
function svgToJsxAttrs(svg: string): string {
const stylePlaceholders: string[] = [];
const withoutStyles = svg.replace(/<style[\s\S]*?<\/style>/gi, (match) => {
stylePlaceholders.push(match);
return `__STYLE_PLACEHOLDER_${stylePlaceholders.length - 1}__`;
});
const converted = withoutStyles
.replace(/\bclass="/g, 'className="')
.replace(/\bxmlns="[^"]*"/g, "")
.replace(/\b([a-z]+)-([a-z])/g, (_, a, b) => `${a}${b.toUpperCase()}`);
return converted.replace(
/__STYLE_PLACEHOLDER_(\d+)__/g,
(_, i) => stylePlaceholders[Number(i)],
);
}| return { | ||
| total: data.total, | ||
| icons: filtered.slice(0, limit), | ||
| }; |
There was a problem hiding this comment.
total in SearchResult always reflects the unfiltered registry count
searchIcons returns total: data.total (the full 5,600+ registry size) even when the result set is heavily filtered. Any future caller relying on total to show "X results found" will display a misleading number. The field would be more useful as the count of matching icons before the limit slice:
return {
total: filtered.length,
icons: filtered.slice(0, limit),
};| export function toJsx(svg: string, componentName: string): string { | ||
| const name = toPascalCase(componentName); | ||
| const jsxSvg = svgToJsxAttrs(svg); | ||
| return `export function ${name}Icon(props) {\n return (\n ${jsxSvg.replace(/<svg/, "<svg {...props}")}\n );\n}`; |
There was a problem hiding this comment.
Generated JSX is unindented for multi-line SVGs
The template literal inserts the SVG string inline without re-indenting it, so every line after the opening <svg> tag will start at column 0 in the copied output:
export function FormatJsonOnlineIcon(props) {
return (
<svg ...>
<defs>... ← no indent
Passing the SVG through a light indent step would produce cleaner output for users pasting into their codebase.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Summary
Test plan
pnpm buildsucceeds within Cloudflare 20K file limit/icons/jinritoutiao/default.svgrenders correctly/icons/format-json-online/default.svgrenders correctly/api/registry.jsonincludes both new icons/api/categories.jsonreturns updated category counts