diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml
index 81a5bc6d..a9e6199d 100644
--- a/docker-compose.staging.yml
+++ b/docker-compose.staging.yml
@@ -46,6 +46,8 @@ services:
SKILLHUB_SECURITY_SCANNER_ENABLED: "true"
SKILLHUB_SECURITY_SCANNER_URL: http://skill-scanner:8000
SKILLHUB_SECURITY_SCANNER_MODE: upload
+ # Local staging needs typed Suite review writes so public Suite market fixtures can be submitted and approved.
+ SKILLHUB_SUITE_REVIEW_WRITES_ENABLED: "true"
depends_on:
postgres:
condition: service_healthy
diff --git a/docs/08-frontend-architecture.md b/docs/08-frontend-architecture.md
index 681d24bc..972f45a4 100644
--- a/docs/08-frontend-architecture.md
+++ b/docs/08-frontend-architecture.md
@@ -28,6 +28,8 @@
|------|------|------|
| 首页 | `/` | 精选/热门/最新、搜索入口 |
| 搜索页 | `/search` | 关键词搜索 + 过滤 + 排序 |
+| 技能套件市场 | `/suites` | 浏览和搜索已发布的技能套件 |
+| 技能套件详情 | `/suite/{namespace}/{slug}` | 套件说明、成员技能和安装计划 |
| 命名空间主页 | `/@{namespace}` | 空间介绍 + 技能列表 |
| 技能详情页 | `/@{namespace}/{slug}` | README 渲染、版本、评分、收藏、下载 |
| 版本历史 | `/@{namespace}/{slug}/versions` | 版本列表 + changelog |
@@ -39,6 +41,7 @@
| 页面 | 路径 | 说明 |
|------|------|------|
| 我的技能 | `/dashboard/skills` | 我发布的技能 + 统一生命周期状态 |
+| 我的技能套件 | `/dashboard/suites` | 创建和管理我维护的技能套件 |
| 发布技能 | `/dashboard/publish` | zip 上传 + 预览 + 提交审核 |
| 我的收藏 | `/dashboard/stars` | 收藏列表 |
| Token 管理 | `/dashboard/tokens` | 创建/查看/吊销 |
diff --git a/web/e2e/dashboard-shell.spec.ts b/web/e2e/dashboard-shell.spec.ts
index d16b86b1..6aba4821 100644
--- a/web/e2e/dashboard-shell.spec.ts
+++ b/web/e2e/dashboard-shell.spec.ts
@@ -15,7 +15,9 @@ test.describe('Dashboard Shell (Real API)', () => {
const sidebar = page.getByRole('complementary')
await expect(sidebar.getByRole('link', { name: 'Profile', exact: true })).toBeVisible()
await expect(sidebar.getByRole('link', { name: 'My Skills', exact: true })).toBeVisible()
+ await expect(sidebar.getByRole('link', { name: 'My Suites', exact: true })).toBeVisible()
await expect(sidebar.getByRole('link', { name: 'API Tokens', exact: true })).toBeVisible()
await expect(page.getByText('View and manage all your published skills')).toBeVisible()
+ await expect(page.getByText('View and manage your skill suites')).toBeVisible()
})
})
diff --git a/web/e2e/theme-toggle.spec.ts b/web/e2e/theme-toggle.spec.ts
index 556df646..0be5ca36 100644
--- a/web/e2e/theme-toggle.spec.ts
+++ b/web/e2e/theme-toggle.spec.ts
@@ -166,7 +166,7 @@ test.describe('Light and dark theme', () => {
window as Window & { __themeAtFirstReactContent?: boolean }
).__themeAtFirstReactContent)).toBe(true)
- await page.getByRole('link', { name: 'Search', exact: true }).first().click()
+ await page.getByRole('link', { name: 'Skill Marketplace', exact: true }).first().click()
await expect(page).toHaveURL(/\/search(?:\?|$)/)
await expect(page.locator('html')).toHaveClass(/dark/)
await expect(page.getByPlaceholder('Search skills...')).toBeVisible()
diff --git a/web/src/app/layout.tsx b/web/src/app/layout.tsx
index 3d6c638f..164582d2 100644
--- a/web/src/app/layout.tsx
+++ b/web/src/app/layout.tsx
@@ -84,11 +84,9 @@ export function Layout() {
}> = [
{ label: t('nav.landing'), to: '/', exact: true },
{ label: t('nav.publish'), to: '/dashboard/publish', auth: true },
- { label: t('nav.search'), to: '/search' },
- { label: t('nav.suites', { defaultValue: '技能套件' }), to: '/suites' },
+ { label: t('nav.marketplace'), to: '/search' },
+ { label: t('nav.suites'), to: '/suites' },
{ label: t('nav.dashboard'), to: '/dashboard', auth: true },
- { label: t('nav.mySkills'), to: '/dashboard/skills', auth: true },
- { label: t('nav.mySuites'), to: '/dashboard/suites', auth: true },
]
const isActive = (to: string, exact?: boolean) => {
diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json
index bc5ef0a0..dd1facca 100644
--- a/web/src/i18n/locales/en.json
+++ b/web/src/i18n/locales/en.json
@@ -13,6 +13,7 @@
"landing": "Home",
"home": "Skill Center",
"search": "Search",
+ "marketplace": "Skill Marketplace",
"skillDetail": "Skill Detail",
"explore": "Explore Skills",
"dashboard": "Dashboard",
@@ -471,6 +472,7 @@
"notifications": "Notifications",
"skillsAndData": "Skills & Data",
"mySkills": "My Skills",
+ "mySuites": "My Suites",
"publish": "Publish",
"namespaces": "Namespaces",
"stars": "My Stars",
@@ -484,6 +486,8 @@
"overview": {
"mySkills": "My Skills",
"mySkillsDesc": "View and manage all your published skills",
+ "mySuites": "My Suites",
+ "mySuitesDesc": "View and manage your skill suites",
"publish": "Publish",
"publishDesc": "Upload and publish a new skill",
"tokens": "API Tokens",
@@ -1559,6 +1563,7 @@
"menu": {
"dashboard": "Dashboard",
"mySkills": "My Skills",
+ "mySuites": "My Suites",
"myNamespaces": "My Namespaces",
"governance": "Governance Center",
"stars": "Starred",
diff --git a/web/src/i18n/locales/ru.json b/web/src/i18n/locales/ru.json
index 13585d05..c7a1c8c9 100644
--- a/web/src/i18n/locales/ru.json
+++ b/web/src/i18n/locales/ru.json
@@ -13,6 +13,7 @@
"landing": "Главная",
"home": "Центр скиллов",
"search": "Поиск",
+ "marketplace": "Каталог навыков",
"skillDetail": "Карточка скилла",
"explore": "Обзор скиллов",
"dashboard": "Панель управления",
@@ -471,6 +472,7 @@
"notifications": "Уведомления",
"skillsAndData": "Скиллы и данные",
"mySkills": "Мои скиллы",
+ "mySuites": "Мои наборы",
"publish": "Публикация",
"namespaces": "Пространства",
"stars": "Мои звёзды",
@@ -484,6 +486,8 @@
"overview": {
"mySkills": "Мои скиллы",
"mySkillsDesc": "Просмотр и управление опубликованными скиллами",
+ "mySuites": "Мои наборы",
+ "mySuitesDesc": "Просмотр и управление наборами навыков",
"publish": "Публикация",
"publishDesc": "Загрузить и опубликовать новый скилл",
"tokens": "Токены API",
@@ -1878,6 +1882,7 @@
"menu": {
"dashboard": "Панель",
"mySkills": "Мои скиллы",
+ "mySuites": "Мои наборы",
"myNamespaces": "Мои пространства имён",
"namespacesAdmin": "Управление пространствами имён",
"governance": "Центр управления",
diff --git a/web/src/i18n/locales/zh.json b/web/src/i18n/locales/zh.json
index c8eb9550..681e3545 100644
--- a/web/src/i18n/locales/zh.json
+++ b/web/src/i18n/locales/zh.json
@@ -13,6 +13,7 @@
"landing": "首页",
"home": "技能中心",
"search": "搜索",
+ "marketplace": "技能市场",
"skillDetail": "技能详情",
"explore": "探索",
"dashboard": "控制台",
@@ -471,6 +472,7 @@
"notifications": "通知设置",
"skillsAndData": "技能与数据",
"mySkills": "我的技能",
+ "mySuites": "我的套件",
"publish": "技能发布",
"namespaces": "命名空间",
"stars": "我的收藏",
@@ -484,6 +486,8 @@
"overview": {
"mySkills": "我的技能",
"mySkillsDesc": "查看和管理你发布的所有技能",
+ "mySuites": "我的套件",
+ "mySuitesDesc": "查看和管理你的技能套件",
"publish": "技能发布",
"publishDesc": "上传并发布新的技能到平台",
"tokens": "Token 凭证",
@@ -1558,6 +1562,7 @@
"menu": {
"dashboard": "控制台",
"mySkills": "我的技能",
+ "mySuites": "我的套件",
"myNamespaces": "我的命名空间",
"governance": "治理中心",
"stars": "我的收藏",
diff --git a/web/src/pages/dashboard.test.tsx b/web/src/pages/dashboard.test.tsx
index acea151d..24584794 100644
--- a/web/src/pages/dashboard.test.tsx
+++ b/web/src/pages/dashboard.test.tsx
@@ -67,6 +67,8 @@ describe('DashboardPage', () => {
expect(html).toContain('sidebar.skillsAndData')
expect(html).toContain('overview.mySkills')
+ expect(html).toContain('sidebar.mySuites')
+ expect(html).toContain('overview.mySuites')
expect(html).not.toContain('overview.publish')
})
})
diff --git a/web/src/pages/dashboard.tsx b/web/src/pages/dashboard.tsx
index 4944e236..e2df560f 100644
--- a/web/src/pages/dashboard.tsx
+++ b/web/src/pages/dashboard.tsx
@@ -5,7 +5,7 @@ import { canViewGovernanceCenter } from '@/shared/lib/governance-access'
import { APP_SHELL_PAGE_CLASS_NAME } from '@/app/page-shell-style'
import { DashboardPageHeader } from '@/shared/components/dashboard-page-header'
import {
- Star, Heart, Package, Key, Shield, Flag, Globe,
+ Star, Heart, Package, Boxes, Key, Shield, Flag, Globe,
UserCog, Lock, Bell, Clock, ChevronRight,
} from 'lucide-react'
@@ -44,6 +44,7 @@ export const SIDEBAR_GROUPS: SidebarGroup[] = [
label: 'sidebar.skillsAndData',
items: [
{ key: 'skills', icon: Package, label: 'sidebar.mySkills', to: '/dashboard/skills' },
+ { key: 'suites', icon: Boxes, label: 'sidebar.mySuites', to: '/dashboard/suites' },
{ key: 'namespaces', icon: Globe, label: 'sidebar.namespaces', to: '/dashboard/namespaces' },
{ key: 'stars', icon: Star, label: 'sidebar.stars', to: '/dashboard/stars' },
{ key: 'subscriptions', icon: Heart, label: 'sidebar.subscriptions', to: '/dashboard/subscriptions' },
@@ -79,6 +80,7 @@ export const SIDEBAR_NAV = SIDEBAR_NAV_ITEMS.map(({ key, icon, label, to, admin,
*/
const OVERVIEW_CARDS = [
{ key: 'skills', icon: Package, label: 'overview.mySkills', to: '/dashboard/skills', desc: 'overview.mySkillsDesc' },
+ { key: 'suites', icon: Boxes, label: 'overview.mySuites', to: '/dashboard/suites', desc: 'overview.mySuitesDesc' },
{ key: 'tokens', icon: Key, label: 'overview.tokens', to: '/dashboard/tokens', desc: 'overview.tokensDesc' },
{ key: 'stars', icon: Star, label: 'overview.stars', to: '/dashboard/stars', desc: 'overview.starsDesc' },
{ key: 'profile', icon: UserCog, label: 'overview.profile', to: '/settings/profile', desc: 'overview.profileDesc' },
diff --git a/web/src/shared/components/user-menu.test.tsx b/web/src/shared/components/user-menu.test.tsx
index b5ac866b..ed8d1b2c 100644
--- a/web/src/shared/components/user-menu.test.tsx
+++ b/web/src/shared/components/user-menu.test.tsx
@@ -77,6 +77,23 @@ describe('user-menu module exports', () => {
})
describe('UserMenu navigation', () => {
+ it('lists personal destinations in the requested order', () => {
+ const html = renderToStaticMarkup(
+ ,
+ )
+
+ expect(html).toContain('user.menu.mySkills')
+ expect(html).toContain('user.menu.mySuites')
+ expect(html).toContain('user.menu.dashboard')
+ expect(html.indexOf('user.menu.mySkills')).toBeLessThan(html.indexOf('user.menu.mySuites'))
+ expect(html.indexOf('user.menu.mySuites')).toBeLessThan(html.indexOf('user.menu.dashboard'))
+ })
+
it('keeps dashboard-only personal links out of the compact avatar menu', () => {
const html = renderToStaticMarkup(
+
+ {t('user.menu.mySkills')}
+
+
+ {t('user.menu.mySuites')}
+
{t('user.menu.dashboard')}