Skip to content

feat: add SEO meta tags, canonical URLs, and sitemap - #72

Closed
Coder-soft wants to merge 2 commits into
creatorcluster:mainfrom
Coder-soft:main
Closed

feat: add SEO meta tags, canonical URLs, and sitemap#72
Coder-soft wants to merge 2 commits into
creatorcluster:mainfrom
Coder-soft:main

Conversation

@Coder-soft

@Coder-soft Coder-soft commented Aug 8, 2026

Copy link
Copy Markdown
  • 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

Summary by CodeRabbit

  • New Features

    • Added dynamic page metadata for titles, descriptions, canonical URLs, and social sharing.
    • Added sitemap generation and sitemap discovery for search engines.
    • Added a dismissible announcement banner linking to the music section.
    • Generator pages now load on demand with a loading indicator.
  • Bug Fixes

    • Improved audio and video player loading, retry behavior, and cleanup.
    • Improved resource card updates when titles change.
    • Refined resource filtering and removed outdated announcement popups.
  • Refactor

    • Strengthened type safety and error handling throughout the application.

- 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
@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

@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.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The 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.

Changes

Site discoverability

Layer / File(s) Summary
SEO metadata and sitemap generation
package.json, scripts/generate_sitemap.mjs, public/robots.txt, public/sitemap.xml, src/App.tsx, src/components/Seo.tsx
The build generates static and Supabase-backed sitemap URLs. App.tsx supplies pathname-based metadata through Seo.

Runtime loading and resources

Layer / File(s) Summary
Dynamic media loading
src/components/AudioPlayer.tsx, src/components/VideoPlayer.tsx, vite.config.ts
Audio and video libraries load asynchronously with guarded initialization, retry handling, and cleanup. Manual media chunks were removed.
Resource announcement and card lifecycle
src/components/resources/*, src/pages/ResourcesHub.tsx
Resources now show a dismissible persisted announcement. Legacy popups were removed, Minecraft music bypasses mood filtering, and card effects use stable cleanup.
Lazy generator loading
src/pages/Generators.tsx
Generator modules load lazily inside a Suspense boundary with a loading fallback.

Editor and type quality

Layer / File(s) Summary
Editor and effect lifecycle corrections
src/components/admin/*, src/components/profile/ProfileEditor.tsx, src/pages/BackgroundGenerator.tsx
Hook ordering, callback dependencies, effect suppressions, select value casts, and error narrowing were updated.
Runtime validation and type cleanup
convex/dashboard.ts, scripts/export_resources.ts, src/components/profile/*, src/hooks/*, src/lib/showcases.ts, src/main.tsx, src/pages/*
External records now use runtime guards and typed shapes. Unsafe annotations and a TypeScript suppression were replaced. Several immutable bindings and minor regex or control-flow cleanups were applied.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Possibly related PRs

Suggested reviewers: yxmura

Poem

I hopped through routes beneath the moon,
And found each sitemap trail in tune.
Audio waits, then starts with care,
A banner rests when dismissed there.
Crisp types guide each code-filled burrow.

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary SEO changes, including meta tags, canonical URLs, and sitemap generation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 8, 2026

Copy link
Copy Markdown

Greptile Summary

This 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/5

Not 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

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced proofs for the posted P1 findings and validated them against the corresponding review comments.
  • T-Rex verified the generator static-route baseline, showing the generator produced 26 configured static URLs and that XML parsing succeeded.
  • T-Rex reproduced dynamic data with a local mocked API, observing that '/' and '#' values were not percent-encoded in generated dynamic URLs, so XML escaping alone does not yield valid route URLs.
  • T-Rex reproduced shipped-build: building with pnpm run build produced two URLs in dist/sitemap.xml and omitted 25 configured SPA routes, meaning the deployed sitemap misses generated URLs.
  • T-Rex executed the validation source: the exact focused Node validation script used for all captures, confirming results are reproducible from the artifact source.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (2)

  1. vite.config.ts, line 29-31 (link)

    P1 Build overwrites the generated sitemap

    The production build replaces the sitemap written during prebuild with the vite-plugin-sitemap output. A built dist/sitemap.xml contains only the home URL and a discovered verification route, omitting 25 configured public pages such as /resources, /blogs, /guides, and /generators. Search engines therefore cannot discover most of the site through the advertised sitemap. Use a single sitemap producer, or configure the Vite plugin with the complete static and enriched route set.

    Artifacts

    Focused sitemap build validation source

    • Exact Node script authored and executed to generate, mock, parse, and compare sitemap URLs with SPA routes, with the takeaway that the checks are reproducible.

    Static generator baseline output

    • Executed direct generator run that emitted 26 configured static routes and parsed XML successfully, with the takeaway that static generation works before Vite overwrites it.

    Shipped build sitemap omission failure

    • Executed production build comparison showing `dist/sitemap.xml` represented only 1 of 26 SPA routes and omitted 25 URLs, with the takeaway that deployed sitemap discovery is broken.

    View artifacts

    T-Rex Ran code and verified through T-Rex

  2. General comment

    P1 Build overwrites the generated sitemap with an incomplete two-URL sitemap

    • Bug
      • scripts/generate_sitemap.mjs generates 26 static SPA URLs, but the production build’s dist/sitemap.xml contains only / plus the plugin-discovered Google verification route. The focused build validation found 25 missing routed pages, including /resources, /blogs, /guides, /generators, utilities, legal pages, and guide detail URLs.
    • Cause
      • vite-plugin-sitemap is enabled in vite.config.ts:29-31 and writes dist/sitemap.xml during Vite build after the prebuild generator writes public/sitemap.xml, replacing the generated file with auto-discovered dist routes.
    • Fix
      • Use exactly one sitemap producer. Either remove/configure vite-plugin-sitemap so it does not write sitemap.xml, or configure it with all static and enriched routes; retain the generated public/sitemap.xml as the deployed artifact. Add a build assertion that parses dist/sitemap.xml and checks all public static routes.

    T-Rex Ran code and verified through T-Rex

Reviews (3): Last reviewed commit: "fix: harden player error handling and AP..." | Re-trigger Greptile

Comment thread src/components/profile/FontPicker.tsx Outdated
Comment on lines 58 to 69
@@ -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') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

View artifacts

T-Rex Ran code and verified through T-Rex

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Check the API response type before assigning it to externalFonts.

filter narrows by predicate, but TypeScript does not treat this predicate as a guarantee that each data.files element has id, title, and url. Add the raw response fields to the request/response type and narrow the result to FontOption[] (for example with a user-defined type guard) before calling setExternalFonts.

🤖 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 win

Narrow the JSON resource fields before constructing Resource.

Record<string, unknown> removes compile-time protection for these fields, and some fallback values use unknown properties unsafely (item.ext, item.url). Add a raw resource shape or runtime guard, then narrow each field before assigning it to Resource.

🤖 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 win

Narrow error before reading message.

catch (error: unknown) still requires narrowing before property access. Use instanceof Error for 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 win

Model route with the response union before narrowing.

Record<string, string> narrows item.route to string; the object branch then makes route unreachable and route.light || route.dark is invalid. Type the parsed response as string | { 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 win

Regenerate Supabase types for folder_id.

folder_id is already in supabase/migrations/20260217120000_create_user_favorites.sql, so the generated user_favorites.update contract should include it. Regenerate Supabase types, then remove the @ts-expect-error at src/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 value

Replace payload as never with the generated profile update type.

The Supabase client is typed with Database, but src/integrations/supabase/types is missing, so profiles.Update cannot 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 win

Validate the Supabase row before asserting ProfileData.

data as unknown as ProfileData only changes the TypeScript shape; it does not check JSON payload from Supabase. Define a selected-row shape for the omitted email field and validate social_links to entries with string values before passing it to SocialIcon.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

📥 Commits

Reviewing files that changed from the base of the PR and between 507128a and 63428c0.

⛔ Files ignored due to path filters (4)
  • convex/_generated/api.js is excluded by !**/_generated/**
  • convex/_generated/dataModel.d.ts is excluded by !**/_generated/**
  • convex/_generated/server.d.ts is excluded by !**/_generated/**
  • convex/_generated/server.js is excluded by !**/_generated/**
📒 Files selected for processing (32)
  • convex/dashboard.ts
  • package.json
  • public/robots.txt
  • public/sitemap.xml
  • scripts/export_resources.ts
  • scripts/generate_sitemap.mjs
  • src/App.tsx
  • src/components/AudioPlayer.tsx
  • src/components/FeaturedResources.tsx
  • src/components/InfiniteMenu.tsx
  • src/components/Seo.tsx
  • src/components/VideoPlayer.tsx
  • src/components/admin/AdminCreatorPacksManager.tsx
  • src/components/admin/BlogEditor.tsx
  • src/components/profile/FontPicker.tsx
  • src/components/profile/ImageUpload.tsx
  • src/components/profile/ProfileEditor.tsx
  • src/components/profile/SvglPicker.tsx
  • src/components/resources/ResourceAnnouncementBanner.tsx
  • src/components/resources/ResourceCard.tsx
  • src/hooks/useProfile.ts
  • src/hooks/useUserFavorites.ts
  • src/lib/showcases.ts
  • src/main.tsx
  • src/pages/BackgroundGenerator.tsx
  • src/pages/BlogView.tsx
  • src/pages/Blogs.tsx
  • src/pages/Generators.tsx
  • src/pages/Profile.tsx
  • src/pages/ResourcesHub.tsx
  • src/pages/Showcase.tsx
  • vite.config.ts
💤 Files with no reviewable changes (1)
  • src/components/InfiniteMenu.tsx

Comment thread package.json
Comment thread scripts/generate_sitemap.mjs
Comment thread scripts/generate_sitemap.mjs Outdated
Comment thread scripts/generate_sitemap.mjs Outdated
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 }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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' \
  . || true

Repository: 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"]))
PY

Repository: 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 || true

Repository: 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.

Comment thread src/App.tsx
Comment on lines +29 to +34
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} />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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 || true

Repository: 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 || true

Repository: 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 || true

Repository: 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.

Comment thread src/components/resources/ResourceCard.tsx
Comment thread src/components/Seo.tsx
Comment thread src/components/VideoPlayer.tsx Outdated
Comment thread src/lib/showcases.ts Outdated
Comment thread src/pages/BackgroundGenerator.tsx Outdated
- 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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
src/integrations/supabase/types.d.ts (1)

3-10: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Replace placeholder schema types with generated Supabase types.

Every schema member is any. Therefore, the update type at src/hooks/useProfile.ts Line 94 is also any and 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

📥 Commits

Reviewing files that changed from the base of the PR and between 63428c0 and 5f9aa8e.

📒 Files selected for processing (24)
  • package.json
  • public/sitemap.xml
  • scripts/generate_sitemap.mjs
  • src/App.tsx
  • src/components/AudioPlayer.tsx
  • src/components/FeaturedResources.tsx
  • src/components/Seo.tsx
  • src/components/VideoPlayer.tsx
  • src/components/admin/AdminCreatorPacksManager.tsx
  • src/components/admin/BlogEditor.tsx
  • src/components/profile/FontPicker.tsx
  • src/components/profile/ImageUpload.tsx
  • src/components/profile/ProfileEditor.tsx
  • src/components/profile/SvglPicker.tsx
  • src/components/resources/ResourceAnnouncementBanner.tsx
  • src/components/resources/ResourceCard.tsx
  • src/hooks/useProfile.ts
  • src/hooks/useUserFavorites.ts
  • src/integrations/supabase/types.d.ts
  • src/lib/showcases.ts
  • src/pages/BackgroundGenerator.tsx
  • src/pages/Profile.tsx
  • src/pages/ResourcesHub.tsx
  • src/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

Comment on lines +36 to +37
setLoadError(false);
setIsReady(false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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.

Comment on lines +37 to +45
.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),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.

Comment on lines +27 to +31
const isFontOption = (font: RawFontOption): font is FontOption =>
typeof font.id === 'number' &&
typeof font.title === 'string' &&
typeof font.url === 'string' &&
font.url.startsWith('https://');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.

Comment on lines +40 to +48
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 '';
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment on lines 62 to 65
player.on('error', () => {
const error = player.error();
console.warn('VideoJS Error:', error);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment thread src/pages/Profile.tsx
Comment on lines +33 to +34
const isSocialLinks = (value: unknown): value is Record<string, string> =>
value === null || (typeof value === 'object' && Object.values(value).every((item) => typeof item === 'string'));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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.

@Coder-soft

Copy link
Copy Markdown
Author

@greptileai pls review

@Coder-soft Coder-soft closed this Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant