Skip to content

chore: merge staging into main#1573

Open
BunsDev wants to merge 17 commits intomainfrom
staging
Open

chore: merge staging into main#1573
BunsDev wants to merge 17 commits intomainfrom
staging

Conversation

@BunsDev
Copy link
Copy Markdown
Member

@BunsDev BunsDev commented Apr 7, 2026

Merge current staging into main.

Includes the latest design-foundation sweep and related staging changes.

Recent staging PRs include:

Co-authored-by: Nova [email protected]

vincentkoc and others added 17 commits April 3, 2026 22:58
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]>
- 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]>
- 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]>
feat: marketplace UI overhaul with main security fixes
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 7, 2026

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

Project Deployment Actions Updated (UTC)
clawhub Ready Ready Preview Apr 7, 2026 1:26pm

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 7, 2026

Greptile Summary

This is a large design-system and staging merge (~11 k additions) covering a unified token/component sweep, a new /users browse page, listPublic/getHoverStats Convex queries, and a seedDemoSkills internal mutation. Two correctness issues in the newly added Convex backend code should be addressed before this lands on main.

Confidence Score: 4/5

Two P1 correctness issues in new Convex backend code should be resolved before merging.

The getHoverStats query uses an unbounded paginate loop in a reactive context and will fail for users with many skills. The seedDemo digest stats mismatch produces inconsistent data from the first seed run. Both are in newly added code paths on the backend.

convex/users.ts (getHoverStats), convex/seedDemo.ts (skillSearchDigest stats)

Prompt To Fix All With AI
This is a comment left during a code review.
Path: convex/users.ts
Line: 467-497

Comment:
**Unbounded paginate loop in reactive query**

The `for (;;)` loop calls `.paginate()` repeatedly until `page.isDone`, pulling every `skills` document owned by the user into a single query transaction. For a prolific publisher this will easily exceed Convex's 8 MB read-bandwidth budget and cause the query to fail at runtime. Because this is a reactive `query` (not an action), every skill write for that user re-runs it for every subscriber watching the hover tooltip — there is no batch-splitting escape hatch available in a query context. Per the project's performance rules, hot read paths must be denormalized rather than issuing unbounded document scans inside a reactive query.

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: 326-332

Comment:
**Digest stats diverge from canonical skills stats**

The `skillSearchDigest` insert calls fresh `Math.random()` for `stats.versions` and `stats.comments` (lines 329–330) instead of reusing the `numVersions`/`numComments` variables already computed for the paired `skills` row (lines 242–243, used at 261–262). From the moment the seed runs, the digest will show different version/comment counts than the authoritative skills document, so any UI that sources counts from the digest will disagree with the underlying record.

```suggestion
          versions: numVersions,
          comments: numComments,
```

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

Reviews (1): Last reviewed commit: "feat: design foundation sweep (#1570) th..." | Re-trigger Greptile

Comment on lines 467 to +497
},
});

/** Lightweight stats for user hover tooltips. Uses the skills by_owner index. */
export const getHoverStats = query({
args: { userId: v.id("users") },
handler: async (ctx, args) => {
const skills = [];
let cursor: string | null = null;

for (;;) {
const page = await ctx.db
.query("skills")
.withIndex("by_owner", (q) => q.eq("ownerUserId", args.userId))
.paginate({ cursor, numItems: 100 });
skills.push(...page.page);
if (page.isDone) {
break;
}
cursor = page.continueCursor;
}

const active = skills.filter((s) => !s.softDeletedAt);
let totalStars = 0;
let totalDownloads = 0;
for (const s of active) {
totalStars += s.stats?.stars ?? 0;
totalDownloads += s.stats?.downloads ?? 0;
}

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

P1 Unbounded paginate loop in reactive query

The for (;;) loop calls .paginate() repeatedly until page.isDone, pulling every skills document owned by the user into a single query transaction. For a prolific publisher this will easily exceed Convex's 8 MB read-bandwidth budget and cause the query to fail at runtime. Because this is a reactive query (not an action), every skill write for that user re-runs it for every subscriber watching the hover tooltip — there is no batch-splitting escape hatch available in a query context. Per the project's performance rules, hot read paths must be denormalized rather than issuing unbounded document scans inside a reactive query.

Prompt To Fix With AI
This is a comment left during a code review.
Path: convex/users.ts
Line: 467-497

Comment:
**Unbounded paginate loop in reactive query**

The `for (;;)` loop calls `.paginate()` repeatedly until `page.isDone`, pulling every `skills` document owned by the user into a single query transaction. For a prolific publisher this will easily exceed Convex's 8 MB read-bandwidth budget and cause the query to fail at runtime. Because this is a reactive `query` (not an action), every skill write for that user re-runs it for every subscriber watching the hover tooltip — there is no batch-splitting escape hatch available in a query context. Per the project's performance rules, hot read paths must be denormalized rather than issuing unbounded document scans inside a reactive query.

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

Comment on lines +326 to +332
statsDownloads: s.downloads,
statsStars: s.stars,
statsInstallsCurrent: Math.floor(s.installs * 0.3),
statsInstallsAllTime: s.installs,
softDeletedAt: undefined,
moderationStatus: "active",
moderationFlags: undefined,
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.

P1 Digest stats diverge from canonical skills stats

The skillSearchDigest insert calls fresh Math.random() for stats.versions and stats.comments (lines 329–330) instead of reusing the numVersions/numComments variables already computed for the paired skills row (lines 242–243, used at 261–262). From the moment the seed runs, the digest will show different version/comment counts than the authoritative skills document, so any UI that sources counts from the digest will disagree with the underlying record.

Suggested change
statsDownloads: s.downloads,
statsStars: s.stars,
statsInstallsCurrent: Math.floor(s.installs * 0.3),
statsInstallsAllTime: s.installs,
softDeletedAt: undefined,
moderationStatus: "active",
moderationFlags: undefined,
versions: numVersions,
comments: numComments,
Prompt To Fix With AI
This is a comment left during a code review.
Path: convex/seedDemo.ts
Line: 326-332

Comment:
**Digest stats diverge from canonical skills stats**

The `skillSearchDigest` insert calls fresh `Math.random()` for `stats.versions` and `stats.comments` (lines 329–330) instead of reusing the `numVersions`/`numComments` variables already computed for the paired `skills` row (lines 242–243, used at 261–262). From the moment the seed runs, the digest will show different version/comment counts than the authoritative skills document, so any UI that sources counts from the digest will disagree with the underlying record.

```suggestion
          versions: numVersions,
          comments: numComments,
```

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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f636b31fca

ℹ️ 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".

Comment on lines +80 to +83
void navigate({
to: "/search",
search: { q, type: undefined },
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Route soul-mode search to souls results

In soul mode, the header search still submits to /search (to: "/search") even though this UI labels the box as “Search souls...”. The new search page only supports skills/plugins/users tabs (src/routes/search.tsx), so soul-mode users can no longer search souls from the primary header control. This is a functional regression from the previous soul-mode flow and will return the wrong result set on souls hosts.

Useful? React with 👍 / 👎.

Comment on lines +477 to +481
for (;;) {
const page = await ctx.db
.query("skills")
.withIndex("by_owner", (q) => q.eq("ownerUserId", args.userId))
.paginate({ cursor, numItems: 100 });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Bound tooltip stats query to avoid full owner scans

getHoverStats paginates through all skills for the owner and accumulates them before returning counts. Because this query is called from user-hover tooltips, high-skill accounts can trigger very expensive reads (and potentially Convex read limits/timeouts), causing slow or failed tooltip loads in production. This should use denormalized counters or a bounded/aggregated approach instead of unbounded pagination.

Useful? React with 👍 / 👎.

Comment on lines +4 to +8
const DEMO_SKILLS = [
{
slug: "mcp-github",
displayName: "MCP GitHub",
summary: "Full GitHub API integration via MCP — issues, PRs, repos, code search, and actions.",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Remove hardcoded skills seeded from repository content

/workspace/clawhub/AGENTS.md states to reject PRs that add skills directly in repo content and require publishing via CLI. This commit introduces a large hardcoded DEMO_SKILLS list and mutation-driven inserts, which bypasses the mandated publication path and embeds skill catalog data directly in source code.

Useful? React with 👍 / 👎.

@BunsDev BunsDev self-assigned this Apr 7, 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.

3 participants