feat: marketplace UI overhaul with main security fixes#1567
Conversation
Complete frontend rebuild of ClawHub into a marketplace-style discovery hub inspired by HuggingFace and npm. No backend changes. Navigation: - Two-row header: brand + search bar + user actions on top, content type tabs (Skills, Plugins) with count badges below - Inline search in navbar navigates to /search - Mobile: search collapses behind icon, tabs scroll horizontally Home page: - Value-prop hero with Browse/Publish CTAs (no duplicate search) - Trending section (8 skills by downloads) - Recently updated section (8 skills by update time) - Staff picks grid (6 highlighted skill cards) - Browse by category grid (8 categories with Lucide icons) - Skeleton loading rows while data fetches Browse pages (Skills + Plugins): - Full-width search bar above sidebar+results grid - Left sidebar with sort options, categories, filter checkboxes (proper ARIA: fieldset/legend, role=radiogroup, aria-checked) - List view uses compact SkillListItem rows (owner/name/summary/meta) - Card view with hover border feedback - View toggle (List/Cards) - Better empty states with guidance text Skill detail page: - README tab as default (was Files) - Two-column layout: tabs+comments on left, metadata sidebar on right - Removed duplicate README from Files tab - Removed duplicate Download button from header (kept in sidebar) - Removed SkillInstallCard from header (license info in sidebar) - Nix/config snippets moved inside two-column layout - Friendly "No README available" instead of raw Convex errors Unified search (/search): - Real search results page (was redirect-only) - Type tabs: All / Skills / Plugins with counts - useUnifiedSearch hook fires skill search + plugin catalog in parallel - Consistent SkillListItem rendering for results Dashboard: - Welcome state for new users with empty dashboard - Simplified header copy Profiles & Footer: - Richer user profiles: large avatar, stat row, SkillListItem for published/starred skills - Multi-column footer: Browse / Publish / Community / Platform Design system: - Spacing tokens: --space-1 (4px) through --space-8 (64px) - Typography scale: --fs-xs through --fs-3xl (8 values, was 42) - Radius tokens: --r-lg/md/sm/xs/pill (renamed from --radius-* to avoid Tailwind CSS v4 variable collision) - Flat buttons (killed gradient, removed hover lift/shadow) - Complete markdown styles: tables, blockquotes, lists, images, hr, heading hierarchy with h1/h2 bottom borders (npm-style) - Removed decorative elements: body gradient backgrounds, card shadows, brand mark animation, category card glow New components: - SkillListItem — compact HF-style row - BrowseSidebar — faceted filter sidebar with ARIA - SkillMetadataSidebar — detail page right sidebar - useUnifiedSearch — parallel search hook - timeAgo — relative time formatter - categories — static skill category taxonomy Seed data: - seedDemo.ts with 20 realistic skills, 5 publishers - repairHighlightedBadges for skillBadges table - repairGlobalStats for correct count Test updates: - Updated 5 test files for new text, class names, and prop changes - All 122 test files, 915 tests passing
- Import internalMutation from convex/functions (not _generated/server) to get trigger wrapping per CLAUDE.md rules - Derive activeCategory from current search query so sidebar category selection shows correct visual/ARIA state - Push moderationStatus filter server-side in repairGlobalStats to avoid full table scan - Reset skillCount/pluginCount to 0 in useUnifiedSearch catch block to prevent stale badge values after search errors
- Remove unused imports (v, getRuntimeEnv) from seedDemo.ts and SkillHeader.tsx - Replace `as any` casts with proper Id<"publishers"> types in seedDemo.ts - Prefix unused params with underscore (_clawdis, _osLabels, _nixSystems, _listDoneLoading) - Remove unused convexSiteUrl variable from SkillHeader
- Switch light theme from warm beige (#f8f2ed) to neutral white (#fafafa) with neutral gray ink (#1a1a1a) and borders (rgba black) - Switch dark theme from warm brown to neutral dark (#111111) with neutral gray borders (rgba white) - Replace fake category grid (8 keyword-search cards) with curated quick links (Most starred, New this week, Browse plugins, Staff picks) - Add "What are skills?" explainer paragraph below hero CTAs - Add fadeIn animation on results list when data arrives - Add "Clear" button in browse results toolbar when filters are active - Tighten browse layout gap from 24px to 16px
Complete visual redesign to a dark, monochrome, terminal-inspired aesthetic inspired by Warp, modern TUI tools, and blueprint designs. Color system: - Default is now dark (#0a0a0a bg, #e0e0e0 ink, #141414 surface) - All accent colors removed — monochrome only (white as accent) - Borders use rgba(255,255,255,0.08) for subtle separation - Light theme available as optional override via [data-theme="light"] Typography: - All fonts now IBM Plex Mono (display, body, code all monospace) - Brand name is lowercase monospace - Section titles are uppercase monospace with letter-spacing - Tags and badges use monospace font Geometry: - All border-radius reduced to 1-2px (sharp TUI corners) - No shadows anywhere (--shadow: none) - No backdrop-filter blur on navbar - Cards, buttons, inputs all have sharp edges Components: - Buttons: transparent bg with border, monospace text - Primary buttons: white on black (inverted) - Tags: border-only, no colored backgrounds - Cards: dark surface with subtle border - Brand mark: 24px square instead of 28px circle Layout: - Replaced category grid with simple quick links - Removed all warm color references - Home section titles are small uppercase labels - Skill list item names use --ink (no accent color)
Theming (P0): - Removed all 78 [data-theme="dark"] override selectors (dark is now default, these were dead code with conflicting warm colors) - Replaced 56 instances of rgba(255,107,74,x) warm coral with rgba(255,255,255,x) monochrome equivalents - Replaced hard-coded warm hex colors (#c35640, #ff6b4a, etc.) with gray monochrome values - CSS file reduced from ~6300 to 5909 lines Touch targets (P1): - Added min-height: 36px to .btn (was ~24px) - Added min-height: 36px to .navbar-tab (was ~27px) - Added min-height: 32px to .sidebar-option and .sidebar-checkbox - Increased padding on buttons and tabs Accessibility (P2/P3): - Comprehensive prefers-reduced-motion: reduce rule — disables all animations AND transitions for users who prefer reduced motion - Covers shimmer, fadeIn, fadeUp, and all CSS transitions
- Home page sections only render when data arrives (no skeleton flash)
- Removed SkeletonRows component from home page (unused)
- Skeleton bars simplified: static gray bars, no shimmer animation
- Skeleton row padding matches list item padding
- Hero padding tightened (48px top → 32px)
- Hero subtitle made concise ("20 skill bundles... Browse, install, publish.")
- Removed redundant explainer paragraph
- Browse results count shows em dash while loading (not "Loading...")
- .section class uses spacing tokens
- .section-title uses monospace font at --fs-lg
- .section-subtitle uses --fs-sm
- results-list removed fadeIn animation (subtle state changes only)
- Replace ~20 remaining warm hex colors in upload/form styles (#ffddc9, #9a3a24, #fff3ec, etc.) with monochrome equivalents - Fix 2 lint errors: remove unused Link import (search.tsx), prefix unused parseDir with underscore (souls/index.tsx) - Add aria-label to PluginListItem and UserListItem for screen reader identification
Keep feature branch UI overhaul (custom CSS) while incorporating security/stability fixes from main: - setSoftDeleted now requires moderation reason (runtime-critical) - moderationNotes displayed in skill detail when available - Rate limit handling for plugin catalog - Tailwind @theme block for auto-merged component compatibility - Capability tag passthrough to SecurityScanResults - ALL_CATEGORY_KEYWORDS export for skills browse model Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR merges
Confidence Score: 4/5Not safe to merge as-is — both Hide/Restore buttons silently fail when a moderator submits a blank reason. Score of 4 (not 5) because of the P1 regression in the setSoftDeleted guard and missing error handler, which directly breaks the moderation workflow this PR claims to fix. src/routes/management.tsx (both setSoftDeleted call sites), src/routes/users/index.tsx (reactive query on public page), convex/users.ts (JS-level filtering). Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/routes/management.tsx
Line: 331-339
Comment:
**Empty reason bypasses guard, error silently dropped**
The guard `if (reason === null) return` catches cancel but not empty string — clicking OK with a blank field passes `reason === ""` to the backend, which throws `ConvexError("Hide reason is required.")`. Because `.catch()` was removed, the failure is silently swallowed and the moderator gets no feedback. The same regression exists at line 600–608 (Skill tools section).
```suggestion
if (!reason?.trim()) return;
void setSoftDeleted({
skillId: skill._id,
deleted: !skill.softDeletedAt,
reason: reason.trim(),
}).catch((error) => window.alert(formatMutationError(error)));
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/routes/management.tsx
Line: 600-608
Comment:
**Same empty-reason / silent-failure regression (Skill tools section)**
This second `setSoftDeleted` call has the identical issue as the one in the Reported skills section above: `reason === null` guard allows empty string through, and `.catch()` was removed so errors are silently swallowed.
```suggestion
if (!reason?.trim()) return;
void setSoftDeleted({
skillId: skill._id,
deleted: !skill.softDeletedAt,
reason: reason.trim(),
}).catch((error) => window.alert(formatMutationError(error)));
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/routes/users/index.tsx
Line: 29-32
Comment:
**`useQuery` on a public listing page violates project policy**
Per `CLAUDE.md`: *"For public listing/browse pages, use `ConvexHttpClient.query()` (one-shot fetch), not `useQuery`/`usePaginatedQuery` (reactive subscription)."* The `/users/` route is a public browse page and should use `convexHttp.query(api.users.listPublic, ...)` (as `useUnifiedSearch.ts` already does) rather than a live reactive subscription.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: convex/users.ts
Line: 446-449
Comment:
**JS-level filtering after full table scan**
Per `CLAUDE.md`/`AGENTS.md`: *"Use compound indexes instead of JS filtering. If you're filtering docs after the query, you're scanning documents you'll throw away."* `queryUsersForPublicList` scans up to 5,000 rows (or more with search) then filters `deletedAt`, `deactivatedAt`, and `handle` presence in JavaScript. A compound index on `(deletedAt, handle)` (or an existing `isActive` denormalized flag) would let Convex skip deleted/deactivated docs entirely.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "merge: integrate origin/main into feat/m..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 39c0fa2531
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Reject empty reason strings in setSoftDeleted calls + re-add .catch() for error feedback (both reported-skills and skill-tools sections) - Replace useQuery with ConvexHttpClient.query() on /users public browse page per CLAUDE.md policy - Add by_active_handle compound index on users table to avoid full table scan in queryUsersForPublicList Co-Authored-By: Claude Opus 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a8a6242f87
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Re-throw non-rate-limit errors in plugin loader so route error boundary handles real failures instead of showing empty results - Bump requestRef on query clear to invalidate in-flight searches and prevent stale results from repopulating - Replace Promise.all with Promise.allSettled in unified search so one failing provider doesn't blank results from other sources - Log unexpected errors in unified search catch block Co-Authored-By: Claude Opus 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5003c1bec8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!cancelled) setLoaded(true); | ||
| }); | ||
|
|
||
| Promise.all([ |
There was a problem hiding this comment.
Handle home data requests independently
This Promise.all fan-out fails fast, so a single transient error (for example countPublicSkills) prevents highlighted, trending, and recent sections from being set even if their requests succeeded. In production this turns a partial backend issue into an effectively empty home feed; switch to per-request error handling (or Promise.allSettled) so successful sections still render.
Useful? React with 👍 / 👎.
| limit: 48, | ||
| search: q, | ||
| }); | ||
| setResult(data as UsersLoaderResult); |
There was a problem hiding this comment.
Guard users search results against stale responses
fetchUsers applies every response unconditionally, so if a slower request for an older query resolves after a newer one, setResult will overwrite the current results with stale data. This is reproducible when submitting searches quickly on slow networks; add a request token/abort check before committing state.
Useful? React with 👍 / 👎.
| void navigate({ | ||
| to: "/search", | ||
| search: { q, type: undefined }, | ||
| }); |
There was a problem hiding this comment.
Route soul-mode navbar searches to soul results
When isSoulMode is true the UI prompts users to "Search souls...", but submit always navigates to /search with unified search params. That route/search hook only queries skills, plugins, and users, so soul-mode search sends users to unrelated results instead of soul discovery. The navigation target should branch for soul mode.
Useful? React with 👍 / 👎.
Summary
origin/mainintofeat/marketplace-ui-overhaul, keeping the feature branch's custom CSS UI overhaul as the basesetSoftDeletednow requires moderation reason (prevents runtime crash after backend schema change)staffModerationNoteprefers storedmoderationNotesover hardcoded fallbacksSecurityScanResultsinSkillHeader@themeblock added for Tailwind compatibility with auto-merged shadcn/UI componentsALL_CATEGORY_KEYWORDSexport added for skills browse modelTest plan
tsc --noEmitpasses clean (0 errors)vitest runpasses (938/938 tests, 124/124 files)