feat: add SEO meta tags, canonical URLs, and sitemap - #72
Conversation
- Generate sitemap.xml at build time and reference it in robots.txt - Add global Seo component with og/twitter meta tags - Lazy-load video.js, wavesurfer.js, and generator tabs - Add dismissible resources announcement banner - Tighten TypeScript types and fix lint warnings
|
@Coder-soft is attempting to deploy a commit to the yamura3's projects Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThe PR adds route-based SEO metadata and sitemap generation, changes media and generator loading to dynamic imports, introduces a resource announcement banner, stabilizes editor effects, validates external data, and replaces unsafe TypeScript annotations. ChangesSite discoverability
Runtime loading and resources
Editor and type quality
Estimated code review effort: 3 (Moderate) | ~30 minutes Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis update adds global SEO metadata, sitemap publishing, lazy-loaded media and generator dependencies, and stronger runtime validation. The production build currently replaces the generated sitemap with an incomplete version, so most public pages are missing from the file provided to search engines. Confidence Score: 4/5Not ready to merge until the production sitemap preserves the full set of public routes. The built site publishes a sitemap that omits most configured public pages, which prevents reliable search-engine discovery of those pages. Files Needing Attention: vite.config.ts
What T-Rex did
|
| @@ -64,7 +64,7 @@ export const FontPicker: React.FC<FontPickerProps> = ({ value, onFontChange }) = | |||
| setExternalFonts(validated); | |||
| } | |||
| return; // Success, exit the loop and function | |||
| } catch (error: any) { | |||
| } catch (error: unknown) { | |||
| clearTimeout(timeoutId); | |||
| if (error.name === 'AbortError') { | |||
There was a problem hiding this comment.
Font loading accesses values outside their declared types
The filter callback declares items with only name and url, but reads f.id and f.title; the explicitly unknown caught error is also read through error.name without narrowing. Focused TypeScript compilation reports TS2339 for all three accesses. Model the untrusted response fields before validating them, and narrow the caught value before reading its name.
Artifacts
Narrow TypeScript harness reproducing the FontPicker property accesses
- Review-authored TypeScript harness maps `FontPicker.tsx` lines 58–69 into an isolated compile target and exercises the three candidate accesses, confirming the check scope.
TypeScript configuration for the narrow FontPicker check
- Review-authored no-emit TypeScript configuration selects only the narrow harness with the project's non-strict-compatible compiler settings, confirming the result is isolated.
TypeScript output showing three invalid property access errors
- Captured output from the one executed `pnpm exec tsc` command in `/home/user/repo` exits 2 and reports TS2339 for `f.id`, `f.title`, and `error.name`, confirming the candidate statement.
There was a problem hiding this comment.
Actionable comments posted: 15
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/components/profile/FontPicker.tsx (1)
58-64: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winCheck the API response type before assigning it to
externalFonts.
filternarrows by predicate, but TypeScript does not treat this predicate as a guarantee that eachdata.fileselement hasid,title, andurl. Add the raw response fields to the request/response type and narrow the result toFontOption[](for example with a user-defined type guard) before callingsetExternalFonts.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/profile/FontPicker.tsx` around lines 58 - 64, Update the API response type used by the FontPicker loading flow to include the raw file fields id, title, and url, then change the filter around validated to a user-defined type guard that narrows matching entries to FontOption[]. Pass only that narrowed result to setExternalFonts.src/components/FeaturedResources.tsx (1)
35-47: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winNarrow the JSON resource fields before constructing
Resource.
Record<string, unknown>removes compile-time protection for these fields, and some fallback values useunknownproperties unsafely (item.ext,item.url). Add a raw resource shape or runtime guard, then narrow each field before assigning it toResource.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/FeaturedResources.tsx` around lines 35 - 47, Add a raw resource type or runtime guard for the objects processed by the map callback in FeaturedResources, and narrow every field before constructing Resource. Ensure fallback values such as item.ext and item.url are validated as compatible scalar values rather than assigning unknown properties directly, while preserving the existing defaults and field mappings.src/pages/Showcase.tsx (1)
365-370: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winNarrow
errorbefore readingmessage.
catch (error: unknown)still requires narrowing before property access. Useinstanceof Errorfor the message path and keep the fallback for other thrown values.Proposed fix
- description: error.message || "Failed to create showcase. Please try again.", + description: error instanceof Error && error.message + ? error.message + : "Failed to create showcase. Please try again.",🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/Showcase.tsx` around lines 365 - 370, In the catch block handling showcase creation, narrow the unknown error before accessing its message: use the Error instance’s message when error is an Error, and retain the existing fallback description for other thrown values. Update the description expression in the catch path without changing the surrounding toast behavior.src/components/profile/SvglPicker.tsx (1)
51-60: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winModel
routewith the response union before narrowing.
Record<string, string>narrowsitem.routetostring; the object branch then makesrouteunreachable androute.light || route.darkis invalid. Type the parsed response asstring | { light?: string; dark?: string }and validate untrusted API data.Proposed type correction
-const formattedIcons: SvglIcon[] = data.map((item: Record<string, string>) => { +type SvglApiIcon = Record<string, unknown> & { + route?: string | { light?: string; dark?: string }; +}; + +const formattedIcons: SvglIcon[] = data.map((item: SvglApiIcon) => {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/profile/SvglPicker.tsx` around lines 51 - 60, Update the response typing and parsing in the formattedIcons mapping so item.route is modeled as string | { light?: string; dark?: string } rather than through Record<string, string>. Validate the untrusted route value before narrowing, then preserve the existing preference order of direct string, light variant, dark variant, and empty fallback.
🧹 Nitpick comments (3)
src/hooks/useUserFavorites.ts (1)
164-169: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winRegenerate Supabase types for
folder_id.
folder_idis already insupabase/migrations/20260217120000_create_user_favorites.sql, so the generateduser_favorites.updatecontract should include it. Regenerate Supabase types, then remove the@ts-expect-erroratsrc/hooks/useUserFavorites.ts:170.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/useUserFavorites.ts` around lines 164 - 169, Regenerate the Supabase generated types so the user_favorites update contract includes the existing folder_id column, then remove the `@ts-expect-error` suppression from the update call in useUserFavorites. Preserve the current folder assignment and query filters.src/hooks/useProfile.ts (1)
83-95: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low valueReplace
payload as neverwith the generated profile update type.The Supabase client is typed with
Database, butsrc/integrations/supabase/typesis missing, soprofiles.Updatecannot resolve from that path. Generate the Supabase types first, then call.update<Database["profiles"]["Update"]>(payload).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/useProfile.ts` around lines 83 - 95, Generate the missing Supabase Database types under src/integrations/supabase/types, import or reference Database in useProfile, and update the profiles mutation to call update with Database["profiles"]["Update"] while passing payload directly. Remove the payload as never cast and preserve the existing field filtering and select('id') behavior.src/pages/Profile.tsx (1)
25-25: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winValidate the Supabase row before asserting
ProfileData.
data as unknown as ProfileDataonly changes the TypeScript shape; it does not check JSON payload from Supabase. Define a selected-row shape for the omittedsocial_linksto entries with string values before passing it toSocialIcon.href.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/pages/Profile.tsx` at line 25, Replace the unsafe `data as unknown as ProfileData` assertion with a selected-row type that omits `email`, and validate the Supabase payload before treating it as profile data. Ensure `social_links` is either null or an object whose values are strings, then pass only the validated entries to `SocialIcon.href`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@package.json`:
- Line 7: Add an engines declaration in package.json specifying Node.js >=20 to
support the top-level await, global fetch, and replaceAll used by the prebuild
script. Keep the existing prebuild command unchanged.
In `@scripts/generate_sitemap.mjs`:
- Around line 4-9: Update the routes array in the sitemap generator to remove
/music-copyright because it resolves through ExternalRedirect. Replace the
single /guides entry with the static guide slugs defined by GuideView in
src/pages/Guides.tsx so each indexable guide page is enumerated individually.
- Line 18: Update the profiles REST query in the sitemap generation flow to
filter results to publicly visible, active, non-deleted profiles, using the
schema’s established visibility/status fields and preserving the existing
non-null username requirement.
- Around line 15-24: Update the Supabase enrichment flow around the three fetch
calls and Promise.all so DNS, TLS, network, or timeout failures cannot reject
the sitemap generation or cancel the build. Add bounded request timeouts and
handle each endpoint independently with a fallback to an empty result
(optionally retrying), while preserving the existing .ok filtering and URL
generation for successful responses.
In `@src/App.tsx`:
- Around line 29-34: Update the routing and SEO rendering around BrowserRouter,
useLocation, and the Seo component so route metadata is available in the initial
HTML before client JavaScript executes. Configure prerendering or SSR for every
route, or move route-name/path generation to build/server render time, while
preserving the existing per-route title and description values during client
navigation.
In `@src/components/admin/BlogEditor.tsx`:
- Around line 38-49: Update the loadBlog callback in BlogEditor.tsx so
setLoading(false) runs in a finally block, including when the Supabase request
rejects; preserve the existing error toast and redirect behavior. Apply the same
loading-state cleanup to the corresponding request in
AdminCreatorPacksManager.tsx at lines 25-30.
In `@src/components/AudioPlayer.tsx`:
- Around line 78-82: Update the cleanup returned by the effect in AudioPlayer to
reset isReady and clear wavesurfer.current when it still references the
destroyed ws instance, before or alongside ws?.destroy(). Preserve other unmount
cleanup behavior so callbacks cannot use the stale WaveSurfer reference during
reinitialization.
- Around line 34-36: Update the dynamic import flow in
src/components/AudioPlayer.tsx at lines 34-36 to catch lazy chunk failures,
clear isLoading, and expose the component’s retry/error state. Update
src/components/VideoPlayer.tsx at lines 39-40 to catch video.js import failures,
remove the appended video-js element, and show the retry/error state instead of
leaving an uninitialized element in the DOM.
In `@src/components/resources/ResourceAnnouncementBanner.tsx`:
- Line 18: Update the Explore action in ResourceAnnouncementBanner and its
ResourcesHub integration so it activates the music category through the existing
selectedCategory/handleCategoryChange flow or a matching stable target. Remove
the hidden class so the action remains accessible on mobile, and ensure the link
no longer points to the nonexistent `#music` anchor.
- Around line 7-11: Update ResourceAnnouncementBanner’s visible initialization
and dismiss handler to use safe helpers around localStorage.getItem and
localStorage.setItem, catching storage failures without propagating them. Treat
read failures as not dismissed, and always call setVisible(false) after a
dismiss attempt so persistence remains best effort.
In `@src/components/resources/ResourceCard.tsx`:
- Line 110: Update the effect in ResourceCard so resource identity changes reset
isFontLoaded before starting the new font load, and use cleanup to invalidate or
cancel the prior asynchronous document.fonts.load completion. Ensure stale
completions cannot set state for the current resource while preserving the
existing behavior for the active resource.
In `@src/components/Seo.tsx`:
- Around line 12-14: Normalize the incoming path in the Seo component before
constructing canonical and related metadata URLs, removing trailing slashes
while preserving the root path as “/”. Use the normalized path in the existing
canonical URL construction so routes such as “/blogs/” produce the same metadata
URL as “/blogs”.
In `@src/components/VideoPlayer.tsx`:
- Around line 23-26: Update the VideoPlayer useEffect and its initializePlayer
flow to coordinate async setup with cleanup: use a cancellation flag and local
player reference, prevent initialization from continuing after cancellation, and
have cleanup destroy the local player and remove the created video element even
if playerRef.current has not yet been assigned. Keep initialization limited to
one player across dependency changes and unmounts.
In `@src/lib/showcases.ts`:
- Around line 126-128: Update the row cast in the loop over data so rows use the
queried profile shape rather than Array<Record<string, unknown>>. Ensure the
concrete profile id type is used when indexing profilesMap, while preserving the
existing truthy-id filtering and assignment.
In `@src/pages/BackgroundGenerator.tsx`:
- Line 37: Replace the loose texture state type in BackgroundGenerator with a
concrete Texture type requiring id, url, and title, and validate each data.files
item against that shape before passing the filtered results to setTextures.
Update the renderer and related texture handling to use Texture consistently.
---
Outside diff comments:
In `@src/components/FeaturedResources.tsx`:
- Around line 35-47: Add a raw resource type or runtime guard for the objects
processed by the map callback in FeaturedResources, and narrow every field
before constructing Resource. Ensure fallback values such as item.ext and
item.url are validated as compatible scalar values rather than assigning unknown
properties directly, while preserving the existing defaults and field mappings.
In `@src/components/profile/FontPicker.tsx`:
- Around line 58-64: Update the API response type used by the FontPicker loading
flow to include the raw file fields id, title, and url, then change the filter
around validated to a user-defined type guard that narrows matching entries to
FontOption[]. Pass only that narrowed result to setExternalFonts.
In `@src/components/profile/SvglPicker.tsx`:
- Around line 51-60: Update the response typing and parsing in the
formattedIcons mapping so item.route is modeled as string | { light?: string;
dark?: string } rather than through Record<string, string>. Validate the
untrusted route value before narrowing, then preserve the existing preference
order of direct string, light variant, dark variant, and empty fallback.
In `@src/pages/Showcase.tsx`:
- Around line 365-370: In the catch block handling showcase creation, narrow the
unknown error before accessing its message: use the Error instance’s message
when error is an Error, and retain the existing fallback description for other
thrown values. Update the description expression in the catch path without
changing the surrounding toast behavior.
---
Nitpick comments:
In `@src/hooks/useProfile.ts`:
- Around line 83-95: Generate the missing Supabase Database types under
src/integrations/supabase/types, import or reference Database in useProfile, and
update the profiles mutation to call update with Database["profiles"]["Update"]
while passing payload directly. Remove the payload as never cast and preserve
the existing field filtering and select('id') behavior.
In `@src/hooks/useUserFavorites.ts`:
- Around line 164-169: Regenerate the Supabase generated types so the
user_favorites update contract includes the existing folder_id column, then
remove the `@ts-expect-error` suppression from the update call in
useUserFavorites. Preserve the current folder assignment and query filters.
In `@src/pages/Profile.tsx`:
- Line 25: Replace the unsafe `data as unknown as ProfileData` assertion with a
selected-row type that omits `email`, and validate the Supabase payload before
treating it as profile data. Ensure `social_links` is either null or an object
whose values are strings, then pass only the validated entries to
`SocialIcon.href`.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 0cc5704f-a761-4702-8369-ccd410fad21f
⛔ Files ignored due to path filters (4)
convex/_generated/api.jsis excluded by!**/_generated/**convex/_generated/dataModel.d.tsis excluded by!**/_generated/**convex/_generated/server.d.tsis excluded by!**/_generated/**convex/_generated/server.jsis excluded by!**/_generated/**
📒 Files selected for processing (32)
convex/dashboard.tspackage.jsonpublic/robots.txtpublic/sitemap.xmlscripts/export_resources.tsscripts/generate_sitemap.mjssrc/App.tsxsrc/components/AudioPlayer.tsxsrc/components/FeaturedResources.tsxsrc/components/InfiniteMenu.tsxsrc/components/Seo.tsxsrc/components/VideoPlayer.tsxsrc/components/admin/AdminCreatorPacksManager.tsxsrc/components/admin/BlogEditor.tsxsrc/components/profile/FontPicker.tsxsrc/components/profile/ImageUpload.tsxsrc/components/profile/ProfileEditor.tsxsrc/components/profile/SvglPicker.tsxsrc/components/resources/ResourceAnnouncementBanner.tsxsrc/components/resources/ResourceCard.tsxsrc/hooks/useProfile.tssrc/hooks/useUserFavorites.tssrc/lib/showcases.tssrc/main.tsxsrc/pages/BackgroundGenerator.tsxsrc/pages/BlogView.tsxsrc/pages/Blogs.tsxsrc/pages/Generators.tsxsrc/pages/Profile.tsxsrc/pages/ResourcesHub.tsxsrc/pages/Showcase.tsxvite.config.ts
💤 Files with no reviewable changes (1)
- src/components/InfiniteMenu.tsx
| if (process.env.VITE_SUPABASE_URL && process.env.VITE_SUPABASE_PUBLISHABLE_KEY) { | ||
| const headers = { apikey: process.env.VITE_SUPABASE_PUBLISHABLE_KEY }; | ||
| const [profiles, packs, blogs] = await Promise.all([ | ||
| fetch(`${process.env.VITE_SUPABASE_URL}/rest/v1/profiles?select=username&username=not.is.null`, { headers }), |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== repo files matching relevant names =="
git ls-files | rg '(^|/)(generate_sitemap\.mjs|.*profile.*|.*rls.*|supabase|schema|.*migration.*)$' || true
echo
echo "== generate_sitemap excerpt =="
if [ -f scripts/generate_sitemap.mjs ]; then
nl -ba scripts/generate_sitemap.mjs | sed -n '1,120p'
fi
echo
echo "== search for profile queries/policies in repo =="
rg -n --hidden --glob '!node_modules' --glob '!dist' --glob '!build' \
'profiles|isSuspended|deleted|visibility|public profile|anonymous|RlsPolicy|RLS|non-public|not\.is\.null|supabase' \
. || trueRepository: creatorcluster/renderdragon.org
Length of output: 1174
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== deterministic parser behavior probe for URL predicate =="
python3 - <<'PY'
from urllib.parse import urlparse, parse_qs, unquote_plus
url = "http://db.example/rest/v1/profiles?select=username&username=not.is.null"
params = parse_qs(unquote_plus(url.partition("?")[2]), keep_blank_values=True)
print("params:", params)
print("username_values:", params.get("username"))
print("has_visibility_predicate:", any(p in params for p in ["isPublic","visibility","deleted_status","isSuspended","is_deleted"]))
PYRepository: creatorcluster/renderdragon.org
Length of output: 356
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== generate_sitemap excerpt =="
sed -n '1,140p' scripts/generate_sitemap.mjs
echo
echo "== relevant migration excerpts =="
sed -n '1,240p' supabase/migrations/20250702100614-84c46566-7fc5-4a4f-86ad-c3e3603c3acc.sql
echo '---'
sed -n '1,240p' supabase/migrations/20260703000000_add_profiles_public_select_policy.sql
echo '---'
sed -n '1,240p' supabase/migrations/20260420000000_creator_packs_rls_policy.sql
echo
echo "== profile schema and RLS usage in migrations =="
rg -n 'profiles|CREATE TABLE[[:space:]]+".*profiles"|RlsPolicy|RLS|CREATE POLICY|anon|public|isSuspended|status|visibility|deleted|soft|active|not\.is\.null' supabase/migrations scripts || trueRepository: creatorcluster/renderdragon.org
Length of output: 15894
Exclude non-public profiles from the sitemap.
The anonymous RLS policy allows SELECT on all profiles with USING (true), and revoke only hides columns. This sitemap filters only username=not.is.null, so any private, inactive, or deleted user with a username is published. Update the RLS predicate to USING (true) only for public profiles, or add an equivalent visibility/status filter to the REST query.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/generate_sitemap.mjs` at line 18, Update the profiles REST query in
the sitemap generation flow to filter results to publicly visible, active,
non-deleted profiles, using the schema’s established visibility/status fields
and preserving the existing non-null username requirement.
| const routeName = location.pathname === '/' ? 'Minecraft Creator Tools & Resources' : | ||
| location.pathname.split('/').filter(Boolean).map((part) => part.replaceAll('-', ' ')).join(' / '); | ||
|
|
||
| return ( | ||
| <> | ||
| <Seo title={`${routeName.replace(/\b\w/g, (letter) => letter.toUpperCase())} | RenderDragon`} description={`Explore ${routeName} on RenderDragon, free tools and resources for Minecraft content creators.`} path={location.pathname} /> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 3 \
'renderToString|renderToPipeableStream|prerender|ssr|createRoot|BrowserRouter|HelmetProvider' \
package.json vite.config.ts src || trueRepository: creatorcluster/renderdragon.org
Length of output: 2419
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect package/scripts/types/config and relevant SEO implementation files without running repo code.
printf '--- package scripts and deps ---\n'
python3 - <<'PY'
import json, pathlib
p = pathlib.Path('package.json')
if p.exists():
data=json.loads(p.read_text())
for k in ['scripts','devDependencies','dependencies']:
print(f'[{k}]')
v=data.get(k,{})
if isinstance(v, dict):
for key,val in v.items():
if any(s in key.lower() or isinstance(val,str) and any(s in val.lower() for s in ['vite','react-router','separate','prerender','render','server','next','remix','vercel','netlify', 'render dragon'])):
print(f'{key}: {val}')
elif k=='scripts':
print(f'{key}: {val}')
PY
printf '\n--- config files ---\n'
git ls-files | rg '(^|/)(vite\.config\.(ts|js)|wrangler\.toml|netlify\.toml|vercel\.json|app\.config\.(ts|js)|render\.yaml|cypress|playwright|index\.html|sitemap|robots)' || true
printf '\n--- vite.config relevant ---\n'
if [ -f vite.config.ts ]; then
nl -ba vite.config.ts | sed -n '1,220p'
fi
if [ -f src/index.html ]; then
printf '\n--- src/index.html ---\n'
nl -ba src/index.html | sed -n '1,160p'
elif [ -f index.html ]; then
printf '\n--- index.html ---\n'
nl -ba index.html | sed -n '1,160p'
fi
printf '\n--- Seo component references ---\n'
rg -n -C 4 'function Seo|const Seo|export .*Seo|<Seo|title=|description=|path=|canonical|og:|twitter:' src || trueRepository: creatorcluster/renderdragon.org
Length of output: 260
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect package metadata and build/deploy config without running repo code.
printf '%s\n' '--- package scripts and deps ---'
python3 - <<'PY'
import json, pathlib
p = pathlib.Path('package.json')
if p.exists():
data=json.loads(p.read_text())
for k in ['scripts','devDependencies','dependencies']:
print(f'[{k}]')
v=data.get(k,{})
if isinstance(v, dict):
for key,val in v.items():
if k=='scripts' or any(s in key.lower() or (isinstance(val,str) and any(s in val.lower() for s in ['vite','react-router','ssr','server','vercel','netlify','render']))):
print(f'{key}: {val}')
PY
printf '%s\n' ''
printf '%s\n' '--- config files ---'
git ls-files | rg '(^|/)(vite\.config\.(ts|js)|wrangler\.toml|netlify\.toml|vercel\.json|app\.config\.(ts|js)|render\.yaml|cypress|playwright|index\.html|sitemap|robots)' || true
printf '%s\n' ''
printf '%s\n' '--- vite.config relevant ---'
if [ -f vite.config.ts ]; then
nl -ba vite.config.ts | sed -n '1,220p'
fi
printf '%s\n' ''
if [ -f src/index.html ]; then
printf '%s\n' '--- src/index.html ---'
nl -ba src/index.html | sed -n '1,160p'
elif [ -f index.html ]; then
printf '%s\n' '--- index.html ---'
nl -ba index.html | sed -n '1,160p'
fi
printf '%s\n' ''
printf '%s\n' '--- route-name/SEO code references ---'
rg -n -C 4 'function Seo|const Seo|export .*Seo|<Seo|title=|description=|path=|canonical|og:|twitter:|renderToString|renderToString|Prerenderer|renderingRoute|ssr|server:' src || trueRepository: creatorcluster/renderdragon.org
Length of output: 845
Make SEO metadata render before client JavaScript executes.
BrowserRouter and useLocation mean this route metadata is only available after hydration, and main.tsx still renders via ReactDOM.createRoot. Since non-JavaScript/social crawlers rely on initial HTML, make sure the deploy prerenders/SSR each route or switch the metadata path to generate route values at build/server render time.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/App.tsx` around lines 29 - 34, Update the routing and SEO rendering
around BrowserRouter, useLocation, and the Seo component so route metadata is
available in the initial HTML before client JavaScript executes. Configure
prerendering or SSR for every route, or move route-name/path generation to
build/server render time, while preserving the existing per-route title and
description values during client navigation.
- add retry UI + failure state to audio/video preview players - validate svgl, font, texture, and profile API payloads at runtime - add guide routes to sitemap and make enrichment fetches timeout-safe - normalize trailing slashes in canonical URLs and fix route name splitting - point announcement Explore button at the music category
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
src/integrations/supabase/types.d.ts (1)
3-10: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftReplace placeholder schema types with generated Supabase types.
Every schema member is
any. Therefore, the update type atsrc/hooks/useProfile.tsLine 94 is alsoanyand cannot reject invalid profile fields or values.Generate and commit the project schema type instead of suppressing
no-explicit-any.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/integrations/supabase/types.d.ts` around lines 3 - 10, Replace the placeholder any-based members in the Database type with the generated Supabase schema types, including Json and the public Tables, Views, Functions, Enums, and CompositeTypes definitions. Ensure useProfile’s update type derives from the generated profile schema so invalid fields and values are rejected, and commit the generated type output rather than suppressing no-explicit-any.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/AudioPlayer.tsx`:
- Around line 36-37: Update the source-change initialization logic in
AudioPlayer to reset isPlaying, currentTime, and duration alongside
setLoadError(false) and setIsReady(false). Ensure the new source starts with
stopped playback and cleared time values rather than retaining state from the
previous player.
In `@src/components/FeaturedResources.tsx`:
- Around line 37-45: Filter rawItems to object records before applying slice and
map in the resource-processing flow, excluding null and primitive entries so
field access in the RawResource mapper is safe. Preserve the existing fallback,
normalization, and featured-resource behavior for valid entries.
In `@src/components/profile/FontPicker.tsx`:
- Around line 27-31: Update isFontOption to accept unknown values and first
verify the entry is a non-null object before accessing id, title, or url;
preserve the existing field-type and HTTPS checks for valid objects so malformed
primitive or null entries are rejected without interrupting font loading.
In `@src/components/profile/SvglPicker.tsx`:
- Around line 40-48: Update routeUrl so it trims each candidate route and only
returns a light or dark value when it is a non-empty string; check the light
route first, then fall back to the dark route when light is empty, otherwise
return an empty string.
In `@src/components/VideoPlayer.tsx`:
- Around line 62-65: Update the Video.js error handler in VideoPlayer to set the
loadError state to true when the active player emits an error, while preserving
the existing warning log so the retry UI becomes available for media load
failures.
In `@src/pages/Profile.tsx`:
- Around line 33-34: Update isSocialLinks to reject arrays and validate every
link as a URL before accepting it. Require each parsed URL to use https,
permitting http only if the application requires it, while preserving the
existing null handling and Record<string, string> type guard.
---
Nitpick comments:
In `@src/integrations/supabase/types.d.ts`:
- Around line 3-10: Replace the placeholder any-based members in the Database
type with the generated Supabase schema types, including Json and the public
Tables, Views, Functions, Enums, and CompositeTypes definitions. Ensure
useProfile’s update type derives from the generated profile schema so invalid
fields and values are rejected, and commit the generated type output rather than
suppressing no-explicit-any.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 1381b747-7aa0-4493-bead-490a2203b72e
📒 Files selected for processing (24)
package.jsonpublic/sitemap.xmlscripts/generate_sitemap.mjssrc/App.tsxsrc/components/AudioPlayer.tsxsrc/components/FeaturedResources.tsxsrc/components/Seo.tsxsrc/components/VideoPlayer.tsxsrc/components/admin/AdminCreatorPacksManager.tsxsrc/components/admin/BlogEditor.tsxsrc/components/profile/FontPicker.tsxsrc/components/profile/ImageUpload.tsxsrc/components/profile/ProfileEditor.tsxsrc/components/profile/SvglPicker.tsxsrc/components/resources/ResourceAnnouncementBanner.tsxsrc/components/resources/ResourceCard.tsxsrc/hooks/useProfile.tssrc/hooks/useUserFavorites.tssrc/integrations/supabase/types.d.tssrc/lib/showcases.tssrc/pages/BackgroundGenerator.tsxsrc/pages/Profile.tsxsrc/pages/ResourcesHub.tsxsrc/pages/Showcase.tsx
💤 Files with no reviewable changes (1)
- src/hooks/useUserFavorites.ts
🚧 Files skipped from review as they are similar to previous changes (11)
- src/components/profile/ImageUpload.tsx
- src/components/resources/ResourceAnnouncementBanner.tsx
- src/App.tsx
- src/components/Seo.tsx
- src/components/admin/BlogEditor.tsx
- src/lib/showcases.ts
- scripts/generate_sitemap.mjs
- src/pages/ResourcesHub.tsx
- src/components/resources/ResourceCard.tsx
- src/components/profile/ProfileEditor.tsx
- public/sitemap.xml
| setLoadError(false); | ||
| setIsReady(false); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Reset playback state when the source changes.
Line 36 resets readiness but retains isPlaying, currentTime, and duration. If src changes during playback, the old player is destroyed but the new player can display the old pause icon and time values. Reset these values when initialization starts.
Proposed fix
setLoadError(false);
setIsReady(false);
+ setIsPlaying(false);
+ setCurrentTime(0);
+ setDuration(0);
+ setIsLoading(!allowPlayBeforeReady);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| setLoadError(false); | |
| setIsReady(false); | |
| setLoadError(false); | |
| setIsReady(false); | |
| setIsPlaying(false); | |
| setCurrentTime(0); | |
| setDuration(0); | |
| setIsLoading(!allowPlayBeforeReady); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/AudioPlayer.tsx` around lines 36 - 37, Update the
source-change initialization logic in AudioPlayer to reset isPlaying,
currentTime, and duration alongside setLoadError(false) and setIsReady(false).
Ensure the new source starts with stopped playback and cleared time values
rather than retaining state from the previous player.
| .map((item: RawResource, idx: number) => ({ | ||
| id: typeof item.id === "number" || typeof item.id === "string" ? item.id : `${catKeys[0]}-${idx}`, | ||
| title: stringValue(item.title) || `Resource ${idx + 1}`, | ||
| category: catKeys[0] as Resource["category"], | ||
| subcategory: item.subcategory || undefined, | ||
| credit: item.credit || undefined, | ||
| filetype: item.filetype || item.ext || undefined, | ||
| download_url: item.download_url || item.url || undefined, | ||
| preview_url: item.preview_url || undefined, | ||
| image_url: item.image_url || undefined, | ||
| software: item.software || undefined, | ||
| description: item.description || undefined, | ||
| subcategory: stringValue(item.subcategory), credit: stringValue(item.credit), | ||
| filetype: stringValue(item.filetype) || stringValue(item.ext), | ||
| download_url: stringValue(item.download_url) || stringValue(item.url), | ||
| preview_url: stringValue(item.preview_url), image_url: stringValue(item.image_url), | ||
| software: stringValue(item.software), description: stringValue(item.description), |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Validate each resource entry before reading its fields.
Array.isArray(rawItems) still permits null and primitive entries. item.id then throws, and the catch block discards all featured resources.
Filter object records before slice and map.
Proposed fix
- const resources: Resource[] = (Array.isArray(rawItems) ? rawItems : [])
+ const resources: Resource[] = (Array.isArray(rawItems) ? rawItems : [])
+ .filter((item): item is RawResource => item !== null && typeof item === "object")
.slice(0, 4)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/FeaturedResources.tsx` around lines 37 - 45, Filter rawItems
to object records before applying slice and map in the resource-processing flow,
excluding null and primitive entries so field access in the RawResource mapper
is safe. Preserve the existing fallback, normalization, and featured-resource
behavior for valid entries.
| const isFontOption = (font: RawFontOption): font is FontOption => | ||
| typeof font.id === 'number' && | ||
| typeof font.title === 'string' && | ||
| typeof font.url === 'string' && | ||
| font.url.startsWith('https://'); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Guard null and primitive font entries.
data.files can contain null or primitive values. isFontOption reads font.id before it validates the entry. One malformed entry prevents all external fonts from loading.
Accept unknown, then verify that the value is a non-null object before reading its fields.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/profile/FontPicker.tsx` around lines 27 - 31, Update
isFontOption to accept unknown values and first verify the entry is a non-null
object before accessing id, title, or url; preserve the existing field-type and
HTTPS checks for valid objects so malformed primitive or null entries are
rejected without interrupting font loading.
| const routeUrl = (route: unknown): string => { | ||
| if (typeof route === 'string') return route; | ||
| if (route && typeof route === 'object') { | ||
| const variants = route as { light?: unknown; dark?: unknown }; | ||
| if (typeof variants.light === 'string') return variants.light; | ||
| if (typeof variants.dark === 'string') return variants.dark; | ||
| } | ||
| return ''; | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use the dark route when the light route is empty.
If route.light is "" and route.dark is valid, routeUrl returns "". The caller rejects the icon instead of using the dark route.
Trim and require a non-empty light route before returning it. Then apply the same check to the dark route.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/profile/SvglPicker.tsx` around lines 40 - 48, Update routeUrl
so it trims each candidate route and only returns a light or dark value when it
is a non-empty string; check the light route first, then fall back to the dark
route when light is empty, otherwise return an empty string.
| player.on('error', () => { | ||
| const error = player.error(); | ||
| console.warn('VideoJS Error:', error); | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Show the retry UI for media load failures.
The error handler only logs the Video.js error. If the video source fails, loadError remains false and the user cannot retry. Set loadError when the active player emits an error.
Proposed fix
player.on('error', () => {
const error = player.error();
console.warn('VideoJS Error:', error);
+ if (!cancelled) {
+ setLoadError(true);
+ }
});🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/VideoPlayer.tsx` around lines 62 - 65, Update the Video.js
error handler in VideoPlayer to set the loadError state to true when the active
player emits an error, while preserving the existing warning log so the retry UI
becomes available for media load failures.
| const isSocialLinks = (value: unknown): value is Record<string, string> => | ||
| value === null || (typeof value === 'object' && Object.values(value).every((item) => typeof item === 'string')); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Restrict social links to safe web URLs.
This guard accepts any string, including javascript: URLs. SocialIcon uses each accepted value as an href, so a profile owner can create a stored script-navigation payload for visitors. It also accepts string arrays as records.
Reject arrays. Parse each value and allow only https: and, if required, http: URLs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/pages/Profile.tsx` around lines 33 - 34, Update isSocialLinks to reject
arrays and validate every link as a URL before accepting it. Require each parsed
URL to use https, permitting http only if the application requires it, while
preserving the existing null handling and Record<string, string> type guard.
|
@greptileai pls review |
Summary by CodeRabbit
New Features
Bug Fixes
Refactor