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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR delivers a large frontend-only marketplace UI overhaul (HuggingFace/npm-style) plus a
Confidence Score: 4/5Not safe to merge as-is: the seed mutation directly violates an explicit AGENTS.md reject-on-sight rule, and two CLAUDE.md Convex performance rules are broken in new code. Three P1 findings in changed files — one explicit policy violation (AGENTS.md seed rule) and two Convex performance rule breaches ( convex/seedDemo.ts (seed policy + table scan), src/routes/skills/index.tsx (reactive subscription on browse page) Prompt To Fix All With AIThis is a comment left during a code review.
Path: convex/seedDemo.ts
Line: 175-342
Comment:
**Seed mutation adds skills directly — violates AGENTS.md policy**
`AGENTS.md` explicitly states: *"Reject PRs that add skills into source code/repo content directly (for example under `skills/` or seed-only additions intended as published skills). Skills must be uploaded/published via CLI."* The 20 entries in `DEMO_SKILLS` are seeded as fully-formed public skill rows (with download counts, stars, digest entries, and publisher records) and will surface as real listings in the UI once `seedDemoSkills` runs. This is exactly the "seed-only additions intended as published skills" pattern the policy prohibits.
**Context Used:** AGENTS.md ([source](https://app.greptile.com/review/custom-context?memory=a1d58d20-b4dd-4cbb-973a-9fd7824e1921))
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: 358-363
Comment:
**`.filter()` on indexed `key` field — full table scan**
`globalStats` has a `by_key` index (confirmed in `schema.ts:1030`). CLAUDE.md requires `.withIndex()` instead of `.filter()` for indexed fields; `.filter()` reads every document in the table. Fix:
```suggestion
const stats = await ctx.db
.query("globalStats")
.withIndex("by_key", (q) => q.eq("key", "default"))
.first();
```
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: 66
Comment:
**`useQuery` on public browse page — use `ConvexHttpClient` instead**
CLAUDE.md states: *"For public listing/browse pages, use `ConvexHttpClient.query()` (one-shot fetch), not `useQuery`/`usePaginatedQuery` (reactive subscription)."* The skill count badge doesn't need live updates and opens an unnecessary reactive subscription that widens the invalidation scope. Replace with `convexHttp.query(api.skills.countPublicSkills, {})` inside a `useEffect`, matching the pattern already used in `src/routes/index.tsx`.
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/lib/useUnifiedSearch.ts
Line: 67-71
Comment:
**3-tuple type annotated but only 2 elements initialised**
The type annotation declares a 3-element tuple but the initializer supplies only `[null, null]`. TypeScript strict mode raises a type error here because the third slot is missing. The third element is later assigned via index-2 access (which works at runtime but is type-unsound). Initialise all three slots:
```suggestion
const promises: [
Promise<unknown> | null,
Promise<{ items: PackageListItem[] }> | null,
Promise<{ items: PublicUser[] }> | null,
] = [null, null, null];
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix: audit fixes — warm colors, lint, AR..." | Re-trigger Greptile |
| export const seedDemoSkills = internalMutation({ | ||
| args: {}, | ||
| handler: async (ctx) => { | ||
| // Check if we already seeded | ||
| const existingSkill = await ctx.db | ||
| .query("skills") | ||
| .withIndex("by_slug", (q) => q.eq("slug", "mcp-github")) | ||
| .first(); | ||
| if (existingSkill) { | ||
| return { seeded: false, reason: "already seeded" }; | ||
| } | ||
|
|
||
| // Create a seed user | ||
| const seedUserId = await ctx.db.insert("users", { | ||
| name: "ClawHub Demo", | ||
| displayName: "ClawHub Demo", | ||
| handle: "clawhub-demo", | ||
| image: undefined, | ||
| role: "admin", | ||
| }); | ||
|
|
||
| // Create publisher accounts | ||
| const publisherIds: string[] = []; | ||
| for (const owner of DEMO_OWNERS) { | ||
| const pubId = await ctx.db.insert("publishers", { | ||
| kind: "org", | ||
| handle: owner.handle, | ||
| displayName: owner.displayName, | ||
| linkedUserId: seedUserId, | ||
| createdAt: Date.now(), | ||
| updatedAt: Date.now(), | ||
| }); | ||
| publisherIds.push(pubId); | ||
|
|
||
| // Add membership | ||
| await ctx.db.insert("publisherMembers", { | ||
| publisherId: pubId as Id<"publishers">, | ||
| userId: seedUserId, | ||
| role: "owner", | ||
| createdAt: Date.now(), | ||
| updatedAt: Date.now(), | ||
| }); | ||
| } | ||
|
|
||
| const now = Date.now(); | ||
| const DAY = 86400000; | ||
|
|
||
| for (let i = 0; i < DEMO_SKILLS.length; i++) { | ||
| const s = DEMO_SKILLS[i]; | ||
| const ownerIdx = i % publisherIds.length; | ||
| const createdDaysAgo = Math.floor(Math.random() * 90) + 7; | ||
| const updatedDaysAgo = Math.floor(Math.random() * createdDaysAgo); | ||
| const createdAt = now - createdDaysAgo * DAY; | ||
| const updatedAt = now - updatedDaysAgo * DAY; | ||
| const version = `${Math.floor(Math.random() * 3) + 1}.${Math.floor(Math.random() * 10)}.${Math.floor(Math.random() * 20)}`; | ||
|
|
||
| const isHighlighted = i < 6; | ||
| const badges = isHighlighted | ||
| ? { highlighted: { byUserId: seedUserId, at: now } } | ||
| : undefined; | ||
|
|
||
| const numVersions = Math.floor(Math.random() * 8) + 1; | ||
| const numComments = Math.floor(Math.random() * 15); | ||
|
|
||
| // Create skill first (without latestVersionId) | ||
| const skillId = await ctx.db.insert("skills", { | ||
| slug: s.slug, | ||
| displayName: s.displayName, | ||
| summary: s.summary, | ||
| ownerUserId: seedUserId, | ||
| ownerPublisherId: publisherIds[ownerIdx] as Id<"publishers">, | ||
| tags: {}, | ||
| badges, | ||
| moderationStatus: "active", | ||
| moderationVerdict: "clean", | ||
| stats: { | ||
| downloads: s.downloads, | ||
| installsCurrent: Math.floor(s.installs * 0.3), | ||
| installsAllTime: s.installs, | ||
| stars: s.stars, | ||
| versions: numVersions, | ||
| comments: numComments, | ||
| }, | ||
| statsDownloads: s.downloads, | ||
| statsStars: s.stars, | ||
| statsInstallsCurrent: Math.floor(s.installs * 0.3), | ||
| statsInstallsAllTime: s.installs, | ||
| createdAt, | ||
| updatedAt, | ||
| }); | ||
|
|
||
| // Create skillBadges entry for highlighted skills | ||
| if (isHighlighted) { | ||
| await ctx.db.insert("skillBadges", { | ||
| skillId, | ||
| kind: "highlighted", | ||
| byUserId: seedUserId, | ||
| at: now, | ||
| }); | ||
| } | ||
|
|
||
| // Now create version with real skillId | ||
| const versionId = await ctx.db.insert("skillVersions", { | ||
| skillId, | ||
| version, | ||
| changelog: `Release ${version} — improvements and bug fixes.`, | ||
| files: [], | ||
| parsed: { frontmatter: {} }, | ||
| createdBy: seedUserId, | ||
| createdAt: updatedAt, | ||
| }); | ||
|
|
||
| // Patch skill with version info | ||
| await ctx.db.patch(skillId, { | ||
| latestVersionId: versionId, | ||
| latestVersionSummary: { | ||
| version, | ||
| createdAt: updatedAt, | ||
| changelog: `Release ${version}`, | ||
| }, | ||
| tags: { latest: versionId }, | ||
| }); | ||
|
|
||
| // Create digest for search | ||
| await ctx.db.insert("skillSearchDigest", { | ||
| skillId, | ||
| slug: s.slug, | ||
| displayName: s.displayName, | ||
| summary: s.summary, | ||
| ownerUserId: seedUserId, | ||
| ownerPublisherId: publisherIds[ownerIdx] as Id<"publishers">, | ||
| ownerHandle: DEMO_OWNERS[ownerIdx].handle, | ||
| ownerName: DEMO_OWNERS[ownerIdx].displayName, | ||
| ownerDisplayName: DEMO_OWNERS[ownerIdx].displayName, | ||
| ownerImage: undefined, | ||
| latestVersionId: versionId, | ||
| latestVersionSummary: { | ||
| version, | ||
| createdAt: updatedAt, | ||
| changelog: `Release ${version}`, | ||
| }, | ||
| tags: { latest: versionId }, | ||
| badges, | ||
| stats: { | ||
| downloads: s.downloads, | ||
| installsCurrent: Math.floor(s.installs * 0.3), | ||
| installsAllTime: s.installs, | ||
| stars: s.stars, | ||
| versions: Math.floor(Math.random() * 8) + 1, | ||
| comments: Math.floor(Math.random() * 15), | ||
| }, | ||
| statsDownloads: s.downloads, | ||
| statsStars: s.stars, | ||
| statsInstallsCurrent: Math.floor(s.installs * 0.3), | ||
| statsInstallsAllTime: s.installs, | ||
| softDeletedAt: undefined, | ||
| moderationStatus: "active", | ||
| moderationFlags: undefined, | ||
| moderationReason: undefined, | ||
| isSuspicious: false, | ||
| createdAt, | ||
| updatedAt, | ||
| }); | ||
| } | ||
|
|
||
| return { seeded: true, count: DEMO_SKILLS.length }; | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Seed mutation adds skills directly — violates AGENTS.md policy
AGENTS.md explicitly states: "Reject PRs that add skills into source code/repo content directly (for example under skills/ or seed-only additions intended as published skills). Skills must be uploaded/published via CLI." The 20 entries in DEMO_SKILLS are seeded as fully-formed public skill rows (with download counts, stars, digest entries, and publisher records) and will surface as real listings in the UI once seedDemoSkills runs. This is exactly the "seed-only additions intended as published skills" pattern the policy prohibits.
Context Used: AGENTS.md (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: convex/seedDemo.ts
Line: 175-342
Comment:
**Seed mutation adds skills directly — violates AGENTS.md policy**
`AGENTS.md` explicitly states: *"Reject PRs that add skills into source code/repo content directly (for example under `skills/` or seed-only additions intended as published skills). Skills must be uploaded/published via CLI."* The 20 entries in `DEMO_SKILLS` are seeded as fully-formed public skill rows (with download counts, stars, digest entries, and publisher records) and will surface as real listings in the UI once `seedDemoSkills` runs. This is exactly the "seed-only additions intended as published skills" pattern the policy prohibits.
**Context Used:** AGENTS.md ([source](https://app.greptile.com/review/custom-context?memory=a1d58d20-b4dd-4cbb-973a-9fd7824e1921))
How can I resolve this? If you propose a fix, please make it concise.| const stats = await ctx.db | ||
| .query("globalStats") | ||
| .filter((q) => q.eq(q.field("key"), "default")) | ||
| .first(); | ||
|
|
||
| if (stats) { |
There was a problem hiding this comment.
.filter() on indexed key field — full table scan
globalStats has a by_key index (confirmed in schema.ts:1030). CLAUDE.md requires .withIndex() instead of .filter() for indexed fields; .filter() reads every document in the table. Fix:
| const stats = await ctx.db | |
| .query("globalStats") | |
| .filter((q) => q.eq(q.field("key"), "default")) | |
| .first(); | |
| if (stats) { | |
| const stats = await ctx.db | |
| .query("globalStats") | |
| .withIndex("by_key", (q) => q.eq("key", "default")) | |
| .first(); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: convex/seedDemo.ts
Line: 358-363
Comment:
**`.filter()` on indexed `key` field — full table scan**
`globalStats` has a `by_key` index (confirmed in `schema.ts:1030`). CLAUDE.md requires `.withIndex()` instead of `.filter()` for indexed fields; `.filter()` reads every document in the table. Fix:
```suggestion
const stats = await ctx.db
.query("globalStats")
.withIndex("by_key", (q) => q.eq("key", "default"))
.first();
```
How can I resolve this? If you propose a fix, please make it concise.| @@ -53,56 +65,142 @@ export function SkillsIndex() { | |||
| const searchInputRef = useRef<HTMLInputElement>(null); | |||
| const totalSkills = useQuery(api.skills.countPublicSkills); | |||
There was a problem hiding this comment.
useQuery on public browse page — use ConvexHttpClient instead
CLAUDE.md states: "For public listing/browse pages, use ConvexHttpClient.query() (one-shot fetch), not useQuery/usePaginatedQuery (reactive subscription)." The skill count badge doesn't need live updates and opens an unnecessary reactive subscription that widens the invalidation scope. Replace with convexHttp.query(api.skills.countPublicSkills, {}) inside a useEffect, matching the pattern already used in src/routes/index.tsx.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/skills/index.tsx
Line: 66
Comment:
**`useQuery` on public browse page — use `ConvexHttpClient` instead**
CLAUDE.md states: *"For public listing/browse pages, use `ConvexHttpClient.query()` (one-shot fetch), not `useQuery`/`usePaginatedQuery` (reactive subscription)."* The skill count badge doesn't need live updates and opens an unnecessary reactive subscription that widens the invalidation scope. Replace with `convexHttp.query(api.skills.countPublicSkills, {})` inside a `useEffect`, matching the pattern already used in `src/routes/index.tsx`.
How can I resolve this? If you propose a fix, please make it concise.| const promises: [ | ||
| Promise<unknown> | null, | ||
| Promise<{ items: PackageListItem[] }> | null, | ||
| Promise<{ items: PublicUser[] }> | null, | ||
| ] = [null, null]; |
There was a problem hiding this comment.
3-tuple type annotated but only 2 elements initialised
The type annotation declares a 3-element tuple but the initializer supplies only [null, null]. TypeScript strict mode raises a type error here because the third slot is missing. The third element is later assigned via index-2 access (which works at runtime but is type-unsound). Initialise all three slots:
| const promises: [ | |
| Promise<unknown> | null, | |
| Promise<{ items: PackageListItem[] }> | null, | |
| Promise<{ items: PublicUser[] }> | null, | |
| ] = [null, null]; | |
| const promises: [ | |
| Promise<unknown> | null, | |
| Promise<{ items: PackageListItem[] }> | null, | |
| Promise<{ items: PublicUser[] }> | null, | |
| ] = [null, null, null]; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/useUnifiedSearch.ts
Line: 67-71
Comment:
**3-tuple type annotated but only 2 elements initialised**
The type annotation declares a 3-element tuple but the initializer supplies only `[null, null]`. TypeScript strict mode raises a type error here because the third slot is missing. The third element is later assigned via index-2 access (which works at runtime but is type-unsound). Initialise all three slots:
```suggestion
const promises: [
Promise<unknown> | null,
Promise<{ items: PackageListItem[] }> | null,
Promise<{ items: PublicUser[] }> | null,
] = [null, null, null];
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 051b1dafcd
ℹ️ 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".
| const scanLimit = normalizedSearch | ||
| ? computeUserSearchScanLimit(args.limit) | ||
| : clampInt(args.limit * 6, args.limit, MAX_USER_SEARCH_SCAN); | ||
| const scannedUsers = await ctx.db.query("users").order("desc").take(scanLimit); |
There was a problem hiding this comment.
Use indexed exact-match in public user search
queryUsersForPublicList only ranks users from the most recent scanLimit rows (order("desc").take(...)), so once there are more users than that window, older accounts are never considered even for exact handle searches. This makes /users and unified search return false negatives for legitimate users who are outside the recent slice. The admin path already handles this by doing an indexed exact lookup first; the public path needs the same pattern to avoid dropping valid results.
Useful? React with 👍 / 👎.
| void navigate({ | ||
| to: "/search", | ||
| search: { q, type: undefined }, |
There was a problem hiding this comment.
Route soul-mode header searches to souls index
The navbar submit handler always navigates to /search, even when isSoulMode is true. In soul mode the UI prompts users to “Search souls…”, but /search is wired to unified skills/plugins/users results, so soul queries are sent to the wrong route and cannot return soul matches. This should branch to /souls (with its search params) in soul mode.
Useful? React with 👍 / 👎.
Summary
--fs-*and--r-*to avoid Tailwind CSS v4 variable collisionNew components
SkillListItem— compact HF-style rowBrowseSidebar— faceted filter sidebar with proper ARIASkillMetadataSidebar— detail page right sidebaruseUnifiedSearch— parallel search hooktimeAgo/categories— utilitiesTest plan
npx vite build— clean, no errorsnpx vitest run— 122 files, 915/915 tests passingbunx convex run seedDemo:seedDemoSkills)