Skip to content

feat(gallery): lucide/flag icon redesign + community marketplace (omnivoice-gallery) - #207

Merged
debpalash merged 4 commits into
mainfrom
debpalash/voice-gallery
May 31, 2026
Merged

feat(gallery): lucide/flag icon redesign + community marketplace (omnivoice-gallery)#207
debpalash merged 4 commits into
mainfrom
debpalash/voice-gallery

Conversation

@debpalash

@debpalash debpalash commented May 31, 2026

Copy link
Copy Markdown
Owner

Builds on the merged #203 gallery. Two commits.

1. Icon redesign — replace emoji with lucide + country flags

Emoji rendered inconsistently across OSes and the cards were invisible on the dark bg.

  • Backend archetypes emit lucide icon names for use-cases and the 24 featured voices.
  • New archetypeIcons.jsx: name→lucide map, accent→country flag (country-flag-icons, tree-shaken to ~11), per-category color scale, color-coded avatar tile, and a CSS now-playing equalizer (respects prefers-reduced-motion).
  • Card redesign: real elevated surfaces, avatar + name + facet sub-line, accent/flag chips, footer (Preview / category-colored Use voice / Open in Designer).

2. Community marketplace — debpalash/omnivoice-gallery

Offloads curated + community content to a standalone repo (added here as a submodule for authoring; loaded at runtime via jsDelivr CDN so the binary stays small).

  • Content repo seeded: manifest.json (24-voice starter pack), JSON schema, CONTRIBUTING, and GitHub submission templates with consent / no-impersonation guardrails.
  • Backend community.py: configurable sources, CDN fetch + offline disk cache, strict validation (invalid presets and non-allow-listed audio URLs are dropped — can't crash synthesis or SSRF), filtering, prefilled submit URL, use (preset→render path; voice→sha256-verified download). 11 tests.
  • Frontend: a third Community zone reusing the redesigned card + "Submit a preset / voice" buttons opening the prefilled GitHub forms.

Local-first preserved: network only on open/refresh; everything cached; the built-in generated archetypes need no network so the gallery is never empty.

Verified locally: backend 84 tests pass, frontend builds clean.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Community Gallery marketplace: browse, preview, and import community-contributed presets and voices; submit new items via generated submission links.
  • Improvements
    • Redesigned gallery cards, archetype icons, flag accents, and “now playing” visuals with improved accessibility and reduced-motion support.
    • New client-side hooks and pagination/filters for community content.
  • Tests
    • Added tests covering community manifest loading and validation.
  • Chores
    • Added gallery submodule and a small frontend dependency for flags.

debpalash and others added 2 commits May 31, 2026 20:27
…emoji)

- backend archetypes emit lucide-react icon *names* (cross-platform; emoji
  render inconsistently across OSes) for use-cases and the 24 featured voices.
- new frontend/src/utils/archetypeIcons.jsx: name→lucide map, accent→country
  flag (country-flag-icons, tree-shaken to ~11), per-category color scale,
  color-coded avatar tile, and a CSS-animated now-playing equalizer
  (prefers-reduced-motion aware).
- card redesign: real elevated surfaces (cards were invisible on the dark bg),
  avatar + name + facet sub-line, accent/flag chips, and a footer with
  Preview / category-colored "Use voice" / Open-in-Designer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…odule

Offloads curated + community gallery content to the standalone
debpalash/omnivoice-gallery repo — added here as a submodule for authoring,
loaded at runtime via the jsDelivr CDN so the binary stays small.

Content repo (seeded + pushed separately): manifest.json (24-voice starter
pack generated from the featured archetypes), a JSON schema, CONTRIBUTING,
and GitHub submission templates carrying consent / no-impersonation guardrails.

Backend (api/routers/community.py): configurable sources (env var > file >
default), CDN fetch with offline disk cache, strict validation (invalid
presets and non-allow-listed audio URLs are dropped, so a bad community entry
can neither crash synthesis nor fetch from an arbitrary host), filtering, the
prefilled submit URL, and "use" (preset → archetype render path; voice →
sha256-verified download). 11 tests.

Frontend: a third gallery zone, "Community", reusing the redesigned card, plus
"Submit a preset / voice" buttons opening the prefilled GitHub forms.

Local-first preserved: network only on open/refresh; everything cached; the
built-in generated archetypes need no network, so the gallery is never empty.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2c56355a-f42b-4eb7-9c0f-fa55b6a04508

📥 Commits

Reviewing files that changed from the base of the PR and between e718c9a and 18bc8e1.

📒 Files selected for processing (4)
  • backend/api/routers/community.py
  • frontend/src/api/community.ts
  • frontend/src/pages/VoiceGallery.css
  • frontend/src/pages/VoiceGallery.jsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • frontend/src/api/community.ts
  • frontend/src/pages/VoiceGallery.css
  • frontend/src/pages/VoiceGallery.jsx
  • backend/api/routers/community.py

📝 Walkthrough

Walkthrough

Adds a community gallery: a git submodule data source, backend manifest loader with strict validation/caching and endpoints to list/filter/use items, tests, frontend icon utilities and hooks, and a redesigned VoiceGallery UI with a new Community zone for browsing, previewing, and adopting community items.

Changes

Community Marketplace Feature

Layer / File(s) Summary
Submodule and data source foundation
.gitmodules, omnivoice-gallery
Adds omnivoice-gallery as a Git submodule and advances its pinned commit.
Icon system migration
backend/core/archetypes.py
Replaces emoji icon identifiers with lucide-react component name strings in USE_CASES and _FEATURED_SPEC.
Community API and backend wiring
backend/api/routers/community.py, backend/main.py
Implements FastAPI community router: configuration resolution, per-source manifest fetching/caching, strict validation (instruction tokens, audio host allow-list, use-cases), merging/dedupe, endpoints /community/sources, /community/manifest, /community/items, /community/submit-url, and POST /community/items/{id}/use to materialize profiles; registers router in app.
Backend validation and endpoint tests
backend/tests/test_community.py
Adds tests that seed a cached manifest and verify loader validation rules, merge/dedup behavior, and HTTP endpoints via a TestClient.
Frontend icon rendering utilities and dependency
frontend/src/utils/archetypeIcons.jsx, frontend/package.json
Adds lucide-react icon mapping, USE_CASE_COLOR palette, ArchetypeIcon/AccentFlag/NowPlaying/ArchetypeAvatar components and adds country-flag-icons dependency.
Frontend API client and query hooks
frontend/src/api/community.ts, frontend/src/api/hooks.ts
Adds TypeScript contracts for community items/manifest/filters, API wrappers for /community endpoints, and TanStack Query hooks useCommunityItems and useCommunityManifest with 5-minute caching.
Frontend archetype card redesign and Community zone UI
frontend/src/pages/VoiceGallery.jsx, frontend/src/pages/VoiceGallery.css, frontend/src/store/gallerySlice.ts
Adds Community tab and CommunityZone component, preview/save flows, redesigned ArchetypeCard using accent colors/ArchetypeIcon/AccentFlag/NowPlaying, updated CSS (elevated tiles, facet chips, footer controls, animated equalizer), and extends GalleryZone to include 'community'.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Browser
  participant UI as CommunityZone UI
  participant client as communityApi
  participant Backend as /community API
  participant Cache as Gallery Cache (DATA_DIR/gallery_cache)
  participant Engine as Archetype Engine

  Browser->>UI: open Community tab
  UI->>client: listCommunityItems(limit=100)
  client->>Backend: GET /community/items
  Backend->>Cache: read per-source cached manifest
  Cache-->>Backend: cached manifests
  Backend-->>client: CommunityPage (items, packs, count, offline)
  client-->>UI: CommunityPage
  UI->>UI: render grid (ArchetypeCard)

  UI->>client: addCommunityItem(item_id, name?)
  client->>Backend: POST /community/items/{item_id}/use
  alt preset item
    Backend->>Engine: render preset to WAV
    Engine-->>Backend: WAV bytes
  else voice item
    Backend->>Backend: download voice audio (host allow-list, optional SHA-256)
  end
  Backend->>Backend: insert voice_profiles row
  Backend-->>client: { profile_id, name }
  client-->>UI: profile created (flash)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the two main changes: icon redesign (lucide/flag) and the new community marketplace feature (omnivoice-gallery integration).
Description check ✅ Passed The description is comprehensive and covers all required template sections: summary, changes, type (new feature), testing notes, and release considerations. However, the Testing section lacks explicit detail about testing methodology, and the Checklist is not filled out with actual checks.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch debpalash/voice-gallery

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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 and usage tips.

@greptile-apps

greptile-apps Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds two features on top of the merged #203 gallery: a lucide/flag icon redesign replacing inconsistent emoji rendering, and a community marketplace zone backed by a new community.py router that fetches manifests from a configurable CDN-hosted content repo, validates strictly, caches offline, and exposes five new endpoints.

  • Icon redesign: archetypeIcons.jsx maps backend-emitted lucide names to SVG components, adds country-flag-icons for accent display, per-category color theming, and a CSS equalizer animation with prefers-reduced-motion support. The backend archetypes.py and _FEATURED_SPEC swap all emoji for lucide component names.
  • Community marketplace: community.py fetches/caches manifest.json from jsDelivr, drops invalid presets (invalid instruct tokens) and unsafe audio URLs (non-allowlisted hosts), and offers a "use" endpoint that routes preset items through the archetype render path and voice items through a SHA-256-verified download — both correctly off the async event loop via asyncio.to_thread. 11 new backend tests cover the no-network validation surface.

Confidence Score: 5/5

Safe to merge — no functional regressions on the critical synthesis or auth paths; the new community router is well-isolated.

The core validation logic (dropping unsafe audio URLs, invalid instruct tokens, unknown use-cases) is thorough and covered by tests. The async threading model is handled correctly throughout community_use. The previously flagged issues (submit-url source injection, blocking HTTP in async context) have both been addressed. Remaining observations are cosmetic or coupling style concerns that do not affect correctness.

VoiceGallery.css — the .archetype-card.list rules were dropped while the list-mode toggle in ArchetypesZone still exists, leaving list view visually broken.

Important Files Changed

Filename Overview
backend/api/routers/community.py New community marketplace backend: CDN manifest fetch, offline cache, strict validation, and use/submit endpoints. Sync HTTP in sync FastAPI routes is handled correctly; previous blocking-in-async and redirect-bypass issues remain open from prior review threads.
backend/core/archetypes.py Replaces emoji icon strings with lucide component names across USE_CASES and _FEATURED_SPEC; purely mechanical substitution with no logic changes.
backend/tests/test_community.py 11 tests covering validation drop rules (invalid instruct, unsafe URL, unknown use_case), manifest merge/dedup, endpoint responses from cache, and submit-URL generation — all non-network, fast.
frontend/src/utils/archetypeIcons.jsx New icon utilities: lucide name→component map, country-flag-icons tree-shaken to 10 flags, per-category color scale, ArchetypeAvatar tile, and CSS-animated NowPlaying equalizer.
frontend/src/pages/VoiceGallery.jsx Adds Community zone tab and CommunityZone component; redesigns ArchetypeCard with avatar tile, NowPlaying equalizer, and footer actions. List-view CSS was removed from VoiceGallery.css but the grid/list toggle in ArchetypesZone remains, leaving list mode visually broken.
frontend/src/pages/VoiceGallery.css Card surface redesign: elevated gradient background, NowPlaying animation with prefers-reduced-motion guard, avatar flag badge, and new footer button styles. .archetype-card.list and its dependent rules were removed while the list-mode toggle still exists.
frontend/src/api/community.ts Clean TypeScript API layer mirroring the five new backend endpoints with typed interfaces; no logic concerns.
frontend/src/api/hooks.ts Adds useCommunityItems and useCommunityManifest React Query hooks with 5-minute stale time, consistent with existing archetype hooks.
frontend/src/store/gallerySlice.ts Adds 'community' to GalleryZone union type; one-line change, no logic concerns.

Sequence Diagram

sequenceDiagram
    participant UI as CommunityZone (Frontend)
    participant API as community.ts (API layer)
    participant BE as community.py (Backend)
    participant CDN as jsDelivr CDN
    participant Cache as Disk Cache
    participant DB as SQLite DB
    participant EB as event_bus

    UI->>API: listCommunityItems(filters)
    API->>BE: GET /community/items
    BE->>Cache: "read manifest (if cached & !refresh)"
    alt cache hit
        Cache-->>BE: manifest JSON
    else cache miss or refresh
        BE->>CDN: GET manifest.json
        CDN-->>BE: manifest JSON
        BE->>Cache: write manifest JSON
    end
    BE-->>API: "{ items, total, ... }"
    API-->>UI: CommunityPage

    UI->>API: addCommunityItem(id, name)
    API->>BE: "POST /community/items/{id}/use"
    BE->>Cache: _load() via asyncio.to_thread
    alt "type == preset"
        BE->>BE: _render_archetype_wav() [await]
    else "type == voice"
        BE->>CDN: httpx download (asyncio.to_thread + SHA-256 verify)
        CDN-->>BE: audio bytes
    end
    BE->>DB: INSERT INTO voice_profiles
    BE->>EB: emit(profiles, created)
    BE-->>API: "{ profile_id, name }"
    API-->>UI: flash Added to your voices

    UI->>API: communitySubmitUrl(type)
    API->>BE: "GET /community/submit-url?type=preset"
    BE-->>API: "{ url: github.com/.../issues/new?template=... }"
    API-->>UI: openExternal(url)
Loading

Fix All in Claude Code

Reviews (2): Last reviewed commit: "refactor(marketplace): rename useCommuni..." | Re-trigger Greptile

Comment thread backend/api/routers/community.py
Comment thread backend/api/routers/community.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
backend/api/routers/community.py (2)

125-149: ⚡ Quick win

Consider logging cache read failures for debugging.

Lines 131-132 and 147-148 silently ignore cache read errors. While the fallback logic is sound, logging these failures would help diagnose cache corruption or permission issues.

📝 Suggested improvement
     if not refresh and cache.exists():
         try:
             return json.loads(cache.read_text(encoding="utf-8"))
         except Exception:
-            pass
+            logger.debug("Cache read failed for %s, fetching from network", source)
     try:
         import httpx
         with httpx.Client(timeout=15.0, follow_redirects=True) as client:
...
         if cache.exists():
             try:
                 return json.loads(cache.read_text(encoding="utf-8"))
             except Exception:
-                pass
+                logger.debug("Cache read failed for %s after network error", source)
         return None
🤖 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 `@backend/api/routers/community.py` around lines 125 - 149, The cache read
failures in _fetch_manifest are currently swallowed in both the initial cache
load (when not refresh) and the fallback after a failed fetch; update those
except blocks to log the exception (e.g., using logger.warning or
logger.exception) including the source and the exception details so cache read
errors (cache.read_text/json.loads) are recorded for debugging while preserving
the existing fallback behavior.

212-269: ⚡ Quick win

Use raise ... from e to preserve exception context.

Line 253 raises an HTTPException after catching a generic Exception but doesn't chain the exceptions. This makes debugging harder for API consumers.

🔗 Proposed fix
     except HTTPException:
         raise
     except Exception as e:
         logger.error("Community 'use' failed", exc_info=True)
-        raise HTTPException(status_code=503, detail=f"Couldn't add this voice right now. Error: {e}")
+        raise HTTPException(status_code=503, detail=f"Couldn't add this voice right now. Error: {e}") from e
🤖 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 `@backend/api/routers/community.py` around lines 212 - 269, In community_use,
the generic except block that catches Exception (the one setting logger.error
and then raising HTTPException) should chain the original exception to preserve
context; change the raise to use exception chaining (raise
HTTPException(status_code=503, detail=...) from e) so the original traceback is
retained for debugging; locate the except Exception as e in the community_use
function (the rendering/downloading try/except) and modify the raise
accordingly.
🤖 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 `@backend/api/routers/community.py`:
- Around line 204-209: The parameter name `type` in function
community_submit_url shadows the Python builtin; rename it (for example to
submission_type) in the function signature and in references inside
community_submit_url, and update its Query declaration accordingly so the
endpoint behavior and template selection ("preset" vs other) remain the same;
ensure any external callers/tests using the query parameter name continue to use
"type" if that query name must be preserved by keeping Query("preset") but
changing only the Python variable name (e.g., submission_type: str =
Query("preset")).
- Around line 173-201: The community_items endpoint uses the parameter name type
which shadows the Python builtin; rename the parameter to a non-built-in
identifier (e.g., item_type) in the community_items signature and update all
internal references (the keep function and filtering where it checks
it.get("type") != ... ) to use that new name, and preserve the external query
name by using FastAPI's Query alias (e.g., item_type: Optional[str] =
Query(None, alias="type")). Ensure no other occurrences still reference the old
name.

In `@frontend/src/api/community.ts`:
- Around line 68-71: The exported function useCommunityItem is incorrectly named
with a "use" prefix which triggers react-hooks lint rules; rename the function
(e.g., to adoptCommunityItem) in frontend/src/api/community.ts (change the
export name from useCommunityItem to adoptCommunityItem) and update all call
sites and imports—specifically replace import and await useCommunityItem(...) in
VoiceGallery.jsx with the new name (adoptCommunityItem) so the module still
performs the same POST behavior without triggering rules-of-hooks.

In `@frontend/src/pages/VoiceGallery.css`:
- Line 423: The CSS keyword in the .now-playing i rule uses "currentColor" with
a capital C which violates stylelint's value-keyword-case; update the background
declaration in the .now-playing i selector to use the lowercase form
"currentcolor" so the rule (background: currentcolor;) conforms to linting and
CI checks.

In `@frontend/src/pages/VoiceGallery.jsx`:
- Around line 395-402: Replace the window.open call inside the submit function
with the project's cross-platform opener: import openExternal from
../api/external and call openExternal(url) after receiving the url from
communitySubmitUrl(type); keep the existing try/catch and flash(t(...)) error
handling but remove window.open usage so desktop (Tauri) builds use
`@tauri-apps/plugin-opener` while web/dev fallback remains intact.

---

Nitpick comments:
In `@backend/api/routers/community.py`:
- Around line 125-149: The cache read failures in _fetch_manifest are currently
swallowed in both the initial cache load (when not refresh) and the fallback
after a failed fetch; update those except blocks to log the exception (e.g.,
using logger.warning or logger.exception) including the source and the exception
details so cache read errors (cache.read_text/json.loads) are recorded for
debugging while preserving the existing fallback behavior.
- Around line 212-269: In community_use, the generic except block that catches
Exception (the one setting logger.error and then raising HTTPException) should
chain the original exception to preserve context; change the raise to use
exception chaining (raise HTTPException(status_code=503, detail=...) from e) so
the original traceback is retained for debugging; locate the except Exception as
e in the community_use function (the rendering/downloading try/except) and
modify the raise accordingly.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a9ebac2b-9d3e-479e-b06e-fa29f18043f0

📥 Commits

Reviewing files that changed from the base of the PR and between 9e04d5c and e718c9a.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock, !**/*.lock, !**/bun.lock
📒 Files selected for processing (13)
  • .gitmodules
  • backend/api/routers/community.py
  • backend/core/archetypes.py
  • backend/main.py
  • backend/tests/test_community.py
  • frontend/package.json
  • frontend/src/api/community.ts
  • frontend/src/api/hooks.ts
  • frontend/src/pages/VoiceGallery.css
  • frontend/src/pages/VoiceGallery.jsx
  • frontend/src/store/gallerySlice.ts
  • frontend/src/utils/archetypeIcons.jsx
  • omnivoice-gallery

Comment thread backend/api/routers/community.py
Comment thread backend/api/routers/community.py
Comment thread frontend/src/api/community.ts Outdated
Comment thread frontend/src/pages/VoiceGallery.css Outdated
Comment thread frontend/src/pages/VoiceGallery.jsx
debpalash and others added 2 commits May 31, 2026 22:16
- community_use: run the blocking manifest read + voice download in a thread
  (asyncio.to_thread) so they don't stall the event loop (greptile P2).
- community_submit_url: validate the `source` override against an owner/repo
  pattern, falling back to the configured default (greptile P1 hardening).
- rename the `type` query param to `item_type` (alias="type") so it no longer
  shadows the Python builtin (coderabbit).
- frontend submit buttons use the canonical openExternal() (Tauri-aware) instead
  of window.open, which doesn't open the system browser in the desktop app.
- lowercase the `currentcolor` CSS keyword (stylelint).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ot a hook)

Avoids the use-prefix on a plain API function (rules-of-hooks smell).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@debpalash
debpalash merged commit 2531223 into main May 31, 2026
15 checks passed
@debpalash
debpalash deleted the debpalash/voice-gallery branch June 12, 2026 10:08
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