Skip to content

feat: UI discovery hub overhaul#1507

Open
vincentkoc wants to merge 12 commits intomainfrom
feat/marketplace-ui-overhaul
Open

feat: UI discovery hub overhaul#1507
vincentkoc wants to merge 12 commits intomainfrom
feat/marketplace-ui-overhaul

Conversation

@vincentkoc
Copy link
Copy Markdown
Member

Summary

Complete frontend rebuild of ClawHub into a marketplace-style discovery hub inspired by HuggingFace and npm. No backend changes.

  • Two-row navigation with content type tabs, count badges, and inline search
  • Home page discovery hub with trending, recently updated, staff picks, and category browsing
  • Browse pages with faceted sidebar filters, sort options, list/card toggle, full-width search
  • README-first detail pages with metadata sidebar (stats, download, tags, publisher)
  • Unified search across skills + plugins with type tabs
  • Design system tokens for spacing (8 values), typography (8 values), radius — renamed to --fs-* and --r-* to avoid Tailwind CSS v4 variable collision
  • Flat, utilitarian visual style — killed gradients, decorative shadows, hover animations
  • Complete markdown rendering — tables, blockquotes, lists, images, heading hierarchy with npm-style borders
  • Seed data — 20 realistic demo skills with publishers and highlighted badges
  • Dashboard welcome state for new users
  • Multi-column footer with Browse/Publish/Community/Platform navigation

New components

  • SkillListItem — compact HF-style row
  • BrowseSidebar — faceted filter sidebar with proper ARIA
  • SkillMetadataSidebar — detail page right sidebar
  • useUnifiedSearch — parallel search hook
  • timeAgo / categories — utilities

Test plan

  • npx vite build — clean, no errors
  • npx vitest run — 122 files, 915/915 tests passing
  • Visual check: home page, /skills, /plugins, skill detail, /search, /dashboard
  • Dark mode renders correctly
  • Mobile responsive at 760px and 520px breakpoints
  • Convex seed data populates correctly (bunx convex run seedDemo:seedDemoSkills)

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
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clawhub Ready Ready Preview, Comment Apr 6, 2026 9:58pm

@vincentkoc vincentkoc changed the title feat: marketplace UI overhaul — HuggingFace/npm-style discovery hub feat: UI discovery hub overhaul Apr 3, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 3, 2026

Greptile Summary

This is a substantial frontend-only marketplace UI overhaul — new discovery hub home page, faceted browse sidebar, unified search page, and HuggingFace/npm-style list rows. All the home-page and Header data-fetching has been correctly migrated to convexHttp.query() one-shot calls, which aligns with the project's Convex performance rules for public listing pages.

Key issues found:

  • convex/seedDemo.ts imports from _generated/server instead of convex/functions.ts — All three mutations (seedDemoSkills, repairGlobalStats, repairHighlightedBadges) bypass the wrapDB trigger layer. convex/functions.ts already exports a wrapped internalMutation; the import just needs to point there.

  • activeCategory={undefined} is hardcoded in src/routes/skills/index.tsx — Category pills in the BrowseSidebar never reflect the active selection visually (or via aria-checked). Clicking a category correctly updates the search query, but the sidebar always shows "All" as active.

  • repairGlobalStats does a full JS .filter() after .collect() — Reads every skillSearchDigest document, then filters by moderationStatus in JavaScript. As the catalog scales this will scan the entire table on each repair run, risking the 32K document read limit.

  • Unused vi import in githubActionsOidc.test.ts — Minor lint warning under Biome/oxlint.

Confidence Score: 3/5

Safe to merge after fixing the internalMutation import in seedDemo.ts — the frontend changes are well-structured and the data-fetching patterns are correct.

Score lowered from 5 primarily due to the internalMutation import bypass — seeding through the unwrapped function skips trigger hooks that keep denormalized tables in sync, which could leave skillSearchDigest or stats rollups in a stale state after seeding. The activeCategory UI bug and full-table scan in the repair mutation are also genuine issues, though lower severity. Frontend changes are extensive but well-tested and follow project conventions.

convex/seedDemo.ts needs attention for the wrong import and the JS table-scan. src/routes/skills/index.tsx needs the activeCategory derivation fix.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: convex/seedDemo.ts
Line: 3

Comment:
**Wrong import — bypasses trigger wrapping**

`internalMutation` is imported from `./_generated/server`, but `convex/functions.ts` exports a wrapped version that includes trigger/`wrapDB` context. Per the project's CLAUDE.md:

> All mutations import from `convex/functions.ts` (not `convex/_generated/server`) to get trigger wrapping.

`convex/functions.ts` line 380 confirms the wrapped export exists:
```ts
export const internalMutation = customMutation(rawInternalMutation, customCtx(triggers.wrapDB));
```

Seeding through the un-wrapped version means denormalized/trigger-synced tables (e.g. `skillSearchDigest` or stats rollups) won't be kept in sync if trigger handlers are registered on these tables.

```suggestion
import { internalMutation } from "./functions";
```

This applies to all three exported mutations in this file (`seedDemoSkills`, `repairGlobalStats`, `repairHighlightedBadges`).

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/skills/index.tsx
Line: 134

Comment:
**`activeCategory` is hardcoded to `undefined` — category selection has no visual feedback**

`activeCategory={undefined}` is a constant, so the "All" radio option always renders with `aria-checked="true"` regardless of which category the user clicked. `handleCategoryChange` correctly updates the search query via `model.onQueryChange`, but there is no inverse mapping from the current `model.query` back to a category slug.

As a result: clicking "MCP Tools" sets the search query to `"mcp"` but the sidebar continues to show "All" as selected. This is both a broken ARIA state (`aria-checked` on the actual category button stays `false`) and a visual UX regression.

A minimal fix is to derive `activeCategory` from the current query:

```ts
const activeCategory = useMemo(() => {
  if (!model.query) return undefined;
  return (
    SKILL_CATEGORIES.find((c) =>
      c.keywords.some((k) => k === model.query.trim().toLowerCase()),
    )?.slug ?? undefined
  );
}, [model.query]);
```

Then pass `activeCategory={activeCategory}` to `<BrowseSidebar>`.

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/seedDemo.ts
Line: 369-376

Comment:
**JS filtering after `.collect()` scans the full `skillSearchDigest` table**

The index query on `by_active_updated` already constrains `softDeletedAt === undefined`, so the additional `!d.softDeletedAt` JS check is redundant. More importantly, the `moderationStatus === "active"` condition is applied entirely in JavaScript after collecting all documents, which reads and bills for the full table. Per AGENTS.md:

> `.filter().collect()` without an index causes full table scans — every doc is read and billed.

Consider using a Convex `.filter()` call to push the predicate server-side:

```ts
const count = await ctx.db
  .query("skillSearchDigest")
  .withIndex("by_active_updated", (q) => q.eq("softDeletedAt", undefined))
  .filter((q) => q.eq(q.field("moderationStatus"), "active"))
  .collect()
  .then((r) => r.length);
```

Even as a repair utility this can time-out on large deployments and hits the 32K document read limit once the real catalog grows.

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/lib/githubActionsOidc.test.ts
Line: 3

Comment:
**Unused `vi` import**

`vi` is imported from `vitest` but is never referenced in this file. This will cause a lint warning under the project's Biome / oxlint setup.

```suggestion
import { describe, expect, it } from "vitest";
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat: marketplace UI overhaul — HuggingF..." | Re-trigger Greptile

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

2 participants