sillyspec 3.7.13 → 3.7.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +77 -0
  2. package/.claude/skills/sillyspec-brainstorm/SKILL.md +591 -0
  3. package/.claude/skills/sillyspec-continue/SKILL.md +44 -0
  4. package/.claude/skills/sillyspec-execute/SKILL.md +233 -0
  5. package/.claude/skills/sillyspec-explore/SKILL.md +96 -0
  6. package/.claude/skills/sillyspec-export/SKILL.md +53 -0
  7. package/.claude/skills/sillyspec-init/SKILL.md +171 -0
  8. package/.claude/skills/sillyspec-plan/SKILL.md +263 -0
  9. package/.claude/skills/sillyspec-propose/SKILL.md +248 -0
  10. package/.claude/skills/sillyspec-quick/SKILL.md +102 -0
  11. package/.claude/skills/sillyspec-resume/SKILL.md +111 -0
  12. package/{templates/scan.md → .claude/skills/sillyspec-scan/SKILL.md} +22 -60
  13. package/.claude/skills/sillyspec-state/SKILL.md +54 -0
  14. package/.claude/skills/sillyspec-status/SKILL.md +131 -0
  15. package/.claude/skills/sillyspec-verify/SKILL.md +146 -0
  16. package/.claude/skills/sillyspec-workspace/SKILL.md +149 -0
  17. package/.sillyspec/changes/run-command-design/design.md +1230 -0
  18. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  19. package/.sillyspec/knowledge/INDEX.md +8 -0
  20. package/.sillyspec/knowledge/uncategorized.md +3 -0
  21. package/.sillyspec/projects/sillyspec.yaml +3 -0
  22. package/README.md +4 -0
  23. package/logo.jpg +0 -0
  24. package/package.json +7 -1
  25. package/packages/dashboard/dist/assets/index-Bx0cgoK_.js +7446 -0
  26. package/packages/dashboard/dist/assets/index-DbkUSsNO.css +1 -0
  27. package/packages/dashboard/dist/favicon.jpg +0 -0
  28. package/packages/dashboard/dist/index.html +2 -2
  29. package/packages/dashboard/dist/logo.jpg +0 -0
  30. package/packages/dashboard/package-lock.json +220 -0
  31. package/packages/dashboard/package.json +8 -5
  32. package/packages/dashboard/public/favicon.jpg +0 -0
  33. package/packages/dashboard/public/logo.jpg +0 -0
  34. package/packages/dashboard/server/index.js +92 -4
  35. package/packages/dashboard/server/parser.js +252 -28
  36. package/packages/dashboard/src/App.vue +139 -9
  37. package/packages/dashboard/src/components/ActionBar.vue +23 -39
  38. package/packages/dashboard/src/components/CommandPalette.vue +40 -65
  39. package/packages/dashboard/src/components/DetailPanel.vue +68 -53
  40. package/packages/dashboard/src/components/DocPreview.vue +137 -20
  41. package/packages/dashboard/src/components/DocTree.vue +48 -26
  42. package/packages/dashboard/src/components/LogStream.vue +12 -32
  43. package/packages/dashboard/src/components/PipelineStage.vue +8 -8
  44. package/packages/dashboard/src/components/PipelineView.vue +35 -43
  45. package/packages/dashboard/src/components/ProjectList.vue +52 -77
  46. package/packages/dashboard/src/components/ProjectOverview.vue +178 -0
  47. package/packages/dashboard/src/components/StageBadge.vue +13 -13
  48. package/packages/dashboard/src/components/StepCard.vue +11 -11
  49. package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -0
  50. package/packages/dashboard/src/components/detail/GitDetail.vue +61 -0
  51. package/packages/dashboard/src/components/detail/TechDetail.vue +43 -0
  52. package/packages/dashboard/src/main.js +4 -1
  53. package/packages/dashboard/src/style.css +13 -13
  54. package/src/index.js +55 -8
  55. package/src/init.js +66 -196
  56. package/src/migrate.js +1 -18
  57. package/src/progress.js +279 -281
  58. package/src/run.js +320 -0
  59. package/src/setup.js +1 -9
  60. package/src/stages/brainstorm.js +210 -0
  61. package/src/stages/execute.js +190 -0
  62. package/src/stages/index.js +22 -0
  63. package/src/stages/plan.js +118 -0
  64. package/src/stages/propose.js +115 -0
  65. package/src/stages/verify.js +98 -0
  66. package/packages/dashboard/dist/assets/index-5Rrvs0Rl.css +0 -1
  67. package/packages/dashboard/dist/assets/index-ZNToqi9V.js +0 -17
  68. package/templates/archive.md +0 -121
  69. package/templates/brainstorm.md +0 -246
  70. package/templates/commit.md +0 -123
  71. package/templates/continue.md +0 -32
  72. package/templates/execute.md +0 -314
  73. package/templates/explore.md +0 -60
  74. package/templates/export.md +0 -21
  75. package/templates/init.md +0 -61
  76. package/templates/plan.md +0 -157
  77. package/templates/progress-format.md +0 -90
  78. package/templates/propose.md +0 -73
  79. package/templates/quick.md +0 -135
  80. package/templates/resume-dialog.md +0 -55
  81. package/templates/resume.md +0 -53
  82. package/templates/scan-quick.md +0 -49
  83. package/templates/skills/playwright-e2e/SKILL.md +0 -340
  84. package/templates/status.md +0 -72
  85. package/templates/verify.md +0 -253
  86. package/templates/workspace-sync.md +0 -89
  87. package/templates/workspace.md +0 -67
  88. /package/.sillyspec/{docs/sillyspec/brainstorm → changes/brainstorm-archive}/2026-04-05-dashboard-design.md +0 -0
  89. /package/.sillyspec/{docs/sillyspec/brainstorm → changes/brainstorm-archive}/2026-04-05-unified-docs-design.md +0 -0
  90. /package/.sillyspec/{specs/2026-04-05-dashboard-design.md → changes/dashboard/design.md.braindraft} +0 -0
  91. /package/.sillyspec/{specs/2026-04-05-unified-docs-design.md → changes/unified-docs-design/design.md} +0 -0
@@ -16,7 +16,7 @@
16
16
  <div class="flex items-center gap-2.5 mb-3">
17
17
  <span
18
18
  class="text-[12px] font-semibold font-[JetBrains_Mono,monospace] tracking-tight"
19
- :style="{ color: isActive ? '#FBBF24' : '#E4E4E7' }"
19
+ :style="{ color: isActive ? '#D97706' : '#1C1C1E' }"
20
20
  >
21
21
  {{ title }}
22
22
  </span>
@@ -25,7 +25,7 @@
25
25
 
26
26
  <!-- Steps -->
27
27
  <div class="space-y-1">
28
- <div v-if="steps.length === 0" class="text-[11px] italic py-1" style="color: #525252;">
28
+ <div v-if="steps.length === 0" class="text-[11px] italic py-1" style="color: #6B7280;">
29
29
  No steps yet
30
30
  </div>
31
31
  <StepCard
@@ -58,13 +58,13 @@ const props = defineProps({
58
58
  const emit = defineEmits(['select-step'])
59
59
 
60
60
  const nodeStyle = computed(() => {
61
- if (props.isActive) return { background: '#FBBF24', boxShadow: '0 0 8px rgba(251,191,36,0.4)' }
61
+ if (props.isActive) return { background: '#D97706', boxShadow: '0 0 8px rgba(251,191,36,0.4)' }
62
62
  const colors = {
63
- 'completed': '#34D399',
64
- 'in-progress': '#FBBF24',
65
- 'blocked': '#FB923C',
66
- 'failed': '#EF4444',
67
- 'pending': '#2A2A2D'
63
+ 'completed': '#16A34A',
64
+ 'in-progress': '#D97706',
65
+ 'blocked': '#EA580C',
66
+ 'failed': '#DC2626',
67
+ 'pending': '#E5E5EA'
68
68
  }
69
69
  return { background: colors[props.status] || colors.pending }
70
70
  })
@@ -1,77 +1,69 @@
1
1
  <template>
2
- <div class="flex flex-col h-full" style="background: #0E0E10;">
2
+ <div class="flex flex-col h-full" style="background: #F5F5F7;">
3
3
  <!-- Header with Tabs -->
4
- <div class="px-6 pt-4 pb-0" style="border-bottom: 1px solid #1F1F22;">
4
+ <div class="px-6 pt-4 pb-0" style="border-bottom: 1px solid #F0F0F3;">
5
5
  <div class="flex items-center gap-6">
6
- <h2 class="text-[11px] font-semibold uppercase tracking-[0.2em] font-[JetBrains_Mono,monospace]" style="color: #525252;">
6
+ <h2 class="text-[11px] font-semibold uppercase tracking-[0.2em] font-[JetBrains_Mono,monospace]" style="color: #6B7280;">
7
7
  {{ project?.name || '项目' }}
8
8
  </h2>
9
- <div class="flex gap-1">
10
- <button
11
- @click="$emit('switch-tab', 'pipeline')"
12
- class="px-3 py-1.5 text-[11px] font-[JetBrains_Mono,monospace] transition-colors rounded"
13
- :style="{ color: activeTab === 'pipeline' ? '#FBBF24' : '#525252', background: activeTab === 'pipeline' ? 'rgba(251,191,36,0.08)' : 'transparent' }"
14
- >流水线</button>
15
- <button
16
- @click="$emit('switch-tab', 'docs')"
17
- class="px-3 py-1.5 text-[11px] font-[JetBrains_Mono,monospace] transition-colors rounded"
18
- :style="{ color: activeTab === 'docs' ? '#FBBF24' : '#525252', background: activeTab === 'docs' ? 'rgba(251,191,36,0.08)' : 'transparent' }"
19
- >文档</button>
20
- </div>
9
+ <n-tabs :value="activeTab" type="segment" size="small" @update:value="$emit('switch-tab', $event)" style="max-width: 200px;">
10
+ <n-tab name="pipeline">流水线</n-tab>
11
+ <n-tab name="docs">文档</n-tab>
12
+ </n-tabs>
21
13
  </div>
22
14
  </div>
23
15
 
24
16
  <!-- Pipeline Tab -->
25
17
  <div v-if="activeTab === 'pipeline'" class="flex flex-col flex-1 overflow-hidden">
26
- <div class="px-6 pt-4 pb-2">
27
- <p v-if="project" class="text-[12px] font-[JetBrains_Mono,monospace]" style="color: #8B8B8E;">
28
- <span style="color: #FBBF24;">{{ currentStage }}</span>
18
+ <div v-if="project && currentStage" class="px-6 pt-4 pb-2">
19
+ <p class="text-[12px] font-[JetBrains_Mono,monospace]" style="color: #636366;">
20
+ <span style="color: #D97706;">{{ currentStage }}</span>
29
21
  </p>
30
22
  </div>
31
23
 
32
24
  <!-- Empty state -->
33
- <div v-if="!project || !project.state" class="flex items-center justify-center flex-1">
34
- <div class="text-center">
35
- <div class="w-14 h-14 mx-auto mb-4 rounded-md flex items-center justify-center" style="border: 1px dashed #2A2A2D; transform: rotate(45deg);">
36
- <svg class="w-5 h-5" style="color: #525252; transform: rotate(-45deg);" fill="none" stroke="currentColor" viewBox="0 0 24 24">
37
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
38
- </svg>
39
- </div>
40
- <p class="text-[12px] font-[JetBrains_Mono,monospace]" style="color: #525252;">选择一个项目查看流水线</p>
41
- </div>
42
- </div>
25
+ <n-empty v-if="!project || !project.state" description="选择一个项目查看流水线" style="margin: auto;" />
43
26
 
44
27
  <!-- Stages -->
45
28
  <div v-else class="flex-1 overflow-y-auto px-6 pb-5 space-y-5">
46
29
  <PipelineStage name="brainstorm" title="头脑风暴" :steps="getStageSteps('brainstorm')" :status="getStageStatus('brainstorm')" :is-active="currentStage === 'brainstorm'" :active-step="activeStep" @select-step="handleSelectStep" />
47
- <div v-if="hasStage('plan')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #1F1F22;" /></div>
30
+ <div v-if="hasStage('plan')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
48
31
  <PipelineStage name="plan" title="规划" :steps="getStageSteps('plan')" :status="getStageStatus('plan')" :is-active="currentStage === 'plan'" :active-step="activeStep" @select-step="handleSelectStep" />
49
- <div v-if="hasStage('execute')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #1F1F22;" /></div>
32
+ <div v-if="hasStage('execute')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
50
33
  <PipelineStage name="execute" title="执行" :steps="getStageSteps('execute')" :status="getStageStatus('execute')" :is-active="currentStage === 'execute'" :active-step="activeStep" @select-step="handleSelectStep" />
51
- <div v-if="hasStage('verify')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #1F1F22;" /></div>
34
+ <div v-if="hasStage('verify')" class="flex items-center pl-[3px]"><div class="w-px h-4" style="background: #F0F0F3;" /></div>
52
35
  <PipelineStage name="verify" title="验证" :steps="getStageSteps('verify')" :status="getStageStatus('verify')" :is-active="currentStage === 'verify'" :active-step="activeStep" @select-step="handleSelectStep" />
53
36
  </div>
54
37
 
55
38
  <!-- Activity Log -->
56
- <div v-if="project?.state?.progress" style="border-top: 1px solid #1F1F22; background: rgba(10,10,11,0.6);">
39
+ <div v-if="project?.state?.progress" style="border-top: 1px solid #F0F0F3; background: rgba(0,0,0,0.03);">
57
40
  <div class="px-6 py-2.5 flex items-center justify-between">
58
- <div class="text-[9px] font-semibold uppercase tracking-[0.25em] font-[JetBrains_Mono,monospace]" style="color: #525252;">活动日志</div>
59
- <div class="text-[10px] font-mono-log" style="color: #3A3A3D;">{{ activityLogs.length }}</div>
41
+ <div class="text-[9px] font-semibold uppercase tracking-[0.25em] font-[JetBrains_Mono,monospace]" style="color: #6B7280;">活动日志</div>
42
+ <div class="text-[10px] font-mono-log" style="color: #D1D1D6;">{{ activityLogs.length }}</div>
60
43
  </div>
61
- <div class="px-6 pb-3 space-y-0.5 max-h-32 overflow-y-auto">
62
- <div v-for="(log, i) in activityLogs" :key="i" class="flex items-start gap-2.5 text-[11px] py-0.5">
63
- <span class="w-10 font-mono-log flex-shrink-0" style="color: #3A3A3D;">{{ log.time }}</span>
64
- <span :style="{ color: log.status === 'completed' ? '#34D399' : '#FBBF24' }">›</span>
65
- <span style="color: #8B8B8E;">{{ log.description }}</span>
66
- </div>
67
- <div v-if="activityLogs.length === 0" class="text-[10px] py-1" style="color: #3A3A3D;">暂无活动记录</div>
44
+ <div class="px-6 pb-3 max-h-32 overflow-y-auto">
45
+ <n-timeline size="small">
46
+ <n-timeline-item
47
+ v-for="(log, i) in activityLogs"
48
+ :key="i"
49
+ :type="log.status === 'completed' ? 'success' : 'warning'"
50
+ >
51
+ <template #default>
52
+ <span class="text-[11px]" style="color: #636366;">{{ log.description }}</span>
53
+ </template>
54
+ <template #time>
55
+ <span class="text-[10px] font-mono-log" style="color: #D1D1D6;">{{ log.time }}</span>
56
+ </template>
57
+ </n-timeline-item>
58
+ </n-timeline>
59
+ <div v-if="activityLogs.length === 0" class="text-[10px] py-1" style="color: #D1D1D6;">暂无活动记录</div>
68
60
  </div>
69
61
  </div>
70
62
  </div>
71
63
 
72
64
  <!-- Docs Tab -->
73
65
  <div v-if="activeTab === 'docs'" class="flex-1 flex overflow-hidden">
74
- <div class="w-[200px] flex-shrink-0 overflow-hidden" style="border-right: 1px solid #1F1F22;">
66
+ <div class="w-[200px] flex-shrink-0 overflow-hidden" style="border-right: 1px solid #F0F0F3;">
75
67
  <DocTree :groups="docs.groups" :selected-file="selectedDocFile" @select-file="$emit('select-doc-file', $event)" />
76
68
  </div>
77
69
  <div class="flex-1 overflow-hidden">
@@ -99,7 +91,7 @@ const props = defineProps({
99
91
 
100
92
  const emit = defineEmits(['select-step', 'switch-tab', 'select-doc-file'])
101
93
 
102
- const currentStage = computed(() => props.project?.state?.currentStage || 'unknown')
94
+ const currentStage = computed(() => props.project?.state?.currentStage || '')
103
95
  const progress = computed(() => props.project?.state?.progress || {})
104
96
  const stages = computed(() => progress.value.stages || {})
105
97
 
@@ -3,138 +3,109 @@
3
3
  <!-- Header -->
4
4
  <div class="relative z-10 px-5 pt-5 pb-4">
5
5
  <div class="flex items-center gap-3">
6
- <div class="w-8 h-8 rounded-md flex items-center justify-center" style="background: linear-gradient(135deg, #FBBF24 0%, #F59E0B 100%); clip-path: polygon(0 0, 100% 0, 85% 100%, 15% 100%);">
6
+ <div class="w-8 h-8 rounded-md flex items-center justify-center" style="background: linear-gradient(135deg, #D97706 0%, #F59E0B 100%); clip-path: polygon(0 0, 100% 0, 85% 100%, 15% 100%);">
7
7
  <span class="text-[10px] font-bold text-black font-[JetBrains_Mono,monospace]">S</span>
8
8
  </div>
9
+ <img src="/logo.jpg" style="width:28px;height:28px;border-radius:6px;margin-right:8px;">
9
10
  <div class="flex-1">
10
- <h1 class="text-[13px] font-semibold tracking-tight font-[JetBrains_Mono,monospace]" style="color: #E4E4E7;">
11
+ <h1 class="text-[13px] font-semibold tracking-tight font-[JetBrains_Mono,monospace]" style="color: #1C1C1E;">
11
12
  SillySpec
12
13
  </h1>
13
- <p class="text-[10px] tracking-widest uppercase" style="color: #525252;">控制台</p>
14
+ <p class="text-[10px] tracking-widest uppercase" style="color: #6B7280;">控制台</p>
14
15
  </div>
15
16
  <!-- Scan paths gear button -->
16
- <button
17
- @click="showScanPanel = !showScanPanel"
18
- class="p-1.5 rounded-sm transition-colors duration-100"
19
- :style="{ color: showScanPanel ? '#FBBF24' : '#525252', background: showScanPanel ? 'rgba(251,191,36,0.08)' : 'transparent' }"
20
- title="扫描路径设置"
21
- >
22
- <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
23
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
24
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
25
- </svg>
26
- </button>
17
+ <n-button quaternary size="tiny" @click="showScanPanel = !showScanPanel" :type="showScanPanel ? 'primary' : 'default'">
18
+ <template #icon>
19
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
20
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
21
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
22
+ </svg>
23
+ </template>
24
+ </n-button>
27
25
  </div>
28
26
 
29
27
  <!-- Scan paths panel (inline) -->
30
28
  <Transition name="slide">
31
- <div v-if="showScanPanel" class="mt-3 rounded-md p-3" style="background: #141416; border: 1px solid #1F1F22;">
32
- <div class="text-[10px] font-semibold uppercase tracking-[0.15em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #525252;">扫描路径</div>
29
+ <div v-if="showScanPanel" class="mt-3 rounded-md p-3" style="background: #FFFFFF; border: 1px solid #F0F0F3;">
30
+ <div class="text-[10px] font-semibold uppercase tracking-[0.15em] mb-2 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">扫描路径</div>
33
31
 
34
- <div v-if="scanPaths.length === 0" class="text-[10px] py-1" style="color: #3A3A3D;">暂无自定义路径</div>
32
+ <div v-if="scanPaths.length === 0" class="text-[10px] py-1" style="color: #D1D1D6;">暂无自定义路径</div>
35
33
  <div v-else class="space-y-1 mb-2">
36
34
  <div v-for="(p, i) in scanPaths" :key="i" class="flex items-center gap-2 text-[10px] group">
37
- <span class="flex-1 truncate font-mono-log" style="color: #8B8B8E;">{{ p }}</span>
38
- <button
39
- @click="removePath(p)"
40
- class="opacity-0 group-hover:opacity-100 transition-opacity text-[9px] px-1 rounded-sm"
41
- style="color: #EF4444; background: rgba(239,68,68,0.08);"
42
- >✕</button>
35
+ <span class="flex-1 truncate font-mono-log" style="color: #636366;">{{ p }}</span>
36
+ <n-button quaternary size="tiny" type="error" @click="removePath(p)">✕</n-button>
43
37
  </div>
44
38
  </div>
45
39
 
46
40
  <!-- Add path -->
47
41
  <div v-if="showAddInput" class="flex items-center gap-2">
48
- <input
49
- ref="pathInput"
50
- v-model="newPath"
51
- type="text"
52
- placeholder="输入目录路径..."
53
- class="flex-1 px-2 py-1 rounded-sm text-[10px] font-mono-log outline-none"
54
- style="background: #0A0A0B; border: 1px solid #2A2A2D; color: #E4E4E7;"
55
- @keydown.enter="addPath"
56
- @keydown.escape="showAddInput = false"
57
- />
58
- <button @click="addPath" class="px-2 py-1 text-[10px] rounded-sm" style="background: rgba(251,191,36,0.1); color: #FBBF24; border: 1px solid rgba(251,191,36,0.2);">添加</button>
42
+ <n-input v-model:value="newPath" size="tiny" placeholder="输入目录路径..." @keydown.enter="addPath" @keydown.escape="showAddInput = false" ref="pathInput" />
43
+ <n-button size="tiny" type="primary" @click="addPath">添加</n-button>
59
44
  </div>
60
- <button
61
- v-else
62
- @click="showAddInput = true"
63
- class="text-[10px] px-2 py-1 rounded-sm transition-colors duration-100"
64
- style="color: #525252; border: 1px dashed #2A2A2D;"
65
- >
66
- + 添加目录
67
- </button>
45
+ <n-button v-else size="tiny" dashed @click="showAddInput = true">+ 添加目录</n-button>
68
46
  </div>
69
47
  </Transition>
70
48
  </div>
71
49
 
72
50
  <!-- Divider -->
73
- <div class="mx-4 h-px" style="background: linear-gradient(90deg, transparent, #2A2A2D, transparent);"></div>
51
+ <div class="mx-4 h-px" style="background: linear-gradient(90deg, transparent, #E5E5EA, transparent);"></div>
74
52
 
75
53
  <!-- Projects List -->
76
54
  <div class="flex-1 overflow-y-auto py-3 relative z-10">
77
55
  <!-- Loading skeleton -->
78
56
  <div v-if="isLoading" class="px-4 space-y-2">
79
- <div v-for="i in 4" :key="i" class="rounded-lg p-3" style="background: #141416;">
80
- <div class="h-3 rounded w-20 skeleton-shimmer mb-2"></div>
81
- <div class="h-2 rounded w-32 skeleton-shimmer"></div>
82
- </div>
83
- <p class="text-center text-[10px] mt-4 font-[JetBrains_Mono,monospace]" style="color: #525252;">
57
+ <n-card v-for="i in 4" :key="i" size="small" :bordered="false">
58
+ <n-skeleton text :width="80" size="small" />
59
+ <n-skeleton text :width="140" size="small" style="margin-top: 6px;" />
60
+ </n-card>
61
+ <p class="text-center text-[10px] mt-4 font-[JetBrains_Mono,monospace]" style="color: #6B7280;">
84
62
  正在扫描项目...
85
63
  </p>
86
64
  </div>
87
65
 
88
66
  <!-- Empty state -->
89
- <div v-else-if="projects.length === 0" class="px-4 py-12 text-center">
90
- <div class="w-10 h-10 mx-auto mb-3 rounded-full flex items-center justify-center" style="border: 1px dashed #2A2A2D;">
91
- <svg class="w-4 h-4" style="color: #525252;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
92
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
93
- </svg>
94
- </div>
95
- <p class="text-[11px]" style="color: #8B8B8E;">未发现 SillySpec 项目</p>
96
- <p class="text-[10px] mt-1" style="color: #525252;">正在扫描项目目录...</p>
97
- </div>
67
+ <n-empty v-else-if="projects.length === 0" description="未发现 SillySpec 项目" style="padding: 48px 0;" />
98
68
 
99
69
  <!-- Projects -->
100
70
  <div v-else class="px-3 space-y-0.5">
101
71
  <div
102
72
  v-for="project in projects"
103
73
  :key="project.path"
104
- :class="[
105
- 'relative rounded-md cursor-pointer transition-all duration-150 overflow-hidden group',
106
- ]"
74
+ :class="['relative rounded-md cursor-pointer transition-all duration-150 overflow-hidden group']"
107
75
  :style="{
108
- background: isActive(project) ? 'rgba(251,191,36,0.06)' : 'transparent',
109
- borderLeft: isActive(project) ? '2px solid #FBBF24' : '2px solid transparent',
76
+ background: isActive(project) ? 'rgba(217,119,6,0.06)' : 'transparent',
77
+ borderLeft: isActive(project) ? '2px solid #D97706' : '2px solid transparent',
110
78
  }"
111
- @mouseenter="$event.currentTarget.style.background = isActive(project) ? 'rgba(251,191,36,0.08)' : 'rgba(255,255,255,0.02)'"
112
- @mouseleave="$event.currentTarget.style.background = isActive(project) ? 'rgba(251,191,36,0.06)' : 'transparent'"
79
+ @mouseenter="$event.currentTarget.style.background = isActive(project) ? 'rgba(217,119,6,0.08)' : 'rgba(255,255,255,0.02)'"
80
+ @mouseleave="$event.currentTarget.style.background = isActive(project) ? 'rgba(217,119,6,0.06)' : 'transparent'"
113
81
  @click="$emit('select', project)"
114
82
  >
115
83
  <div class="px-3 py-2.5">
116
84
  <div class="flex items-center justify-between gap-2">
117
85
  <div class="flex-1 min-w-0">
118
86
  <h3
119
- :class="['text-[12px] font-medium truncate transition-colors duration-150 font-[JetBrains_Mono,monospace]']"
120
- :style="{ color: isActive(project) ? '#FBBF24' : '#E4E4E7' }"
87
+ class="text-[12px] font-medium truncate transition-colors duration-150 font-[JetBrains_Mono,monospace]"
88
+ :style="{ color: isActive(project) ? '#D97706' : '#1C1C1E' }"
121
89
  >
122
90
  {{ project.name }}
123
91
  </h3>
124
- <p class="text-[10px] mt-0.5 truncate font-mono-log" style="color: #525252;">
92
+ <p class="text-[10px] mt-0.5 truncate font-mono-log" style="color: #6B7280;">
125
93
  {{ project.path }}
126
94
  </p>
127
95
  </div>
128
- <StageBadge
96
+ <n-tag
129
97
  v-if="project.state?.currentStage"
130
- :status="getProjectStatus(project)"
131
- :label="stageLabel(project)"
132
- size="sm"
133
- />
98
+ :type="statusTagType(getProjectStatus(project))"
99
+ size="small"
100
+ :bordered="false"
101
+ round
102
+ >
103
+ {{ stageLabel(project) }}
104
+ </n-tag>
134
105
  </div>
135
106
 
136
107
  <!-- Progress -->
137
- <div v-if="project.state?.progress" class="mt-2 h-[2px] rounded-full overflow-hidden" style="background: #1C1C1F;">
108
+ <div v-if="project.state?.progress" class="mt-2 h-[2px] rounded-full overflow-hidden" style="background: #FFFFFF;">
138
109
  <div
139
110
  class="h-full rounded-full transition-all duration-500 progress-gradient"
140
111
  :style="{ width: getProjectProgress(project) + '%' }"
@@ -146,10 +117,10 @@
146
117
  </div>
147
118
 
148
119
  <!-- Footer -->
149
- <div class="relative z-10 px-4 py-2.5" style="border-top: 1px solid #1F1F22;">
120
+ <div class="relative z-10 px-4 py-2.5" style="border-top: 1px solid #F0F0F3;">
150
121
  <div class="flex items-center justify-between">
151
- <span class="text-[10px] font-[JetBrains_Mono,monospace]" style="color: #525252;">{{ projects.length }} 个项目</span>
152
- <kbd class="text-[9px] px-1.5 py-0.5 rounded font-mono-log" style="color: #525252; background: #141416; border: 1px solid #2A2A2D;">⌘K</kbd>
122
+ <span class="text-[10px] font-[JetBrains_Mono,monospace]" style="color: #6B7280;">{{ projects.length }} 个项目</span>
123
+ <kbd class="text-[9px] px-1.5 py-0.5 rounded font-mono-log" style="color: #6B7280; background: #FFFFFF; border: 1px solid #E5E5EA;">⌘K</kbd>
153
124
  </div>
154
125
  </div>
155
126
  </div>
@@ -157,7 +128,6 @@
157
128
 
158
129
  <script setup>
159
130
  import { ref, nextTick, watch } from 'vue'
160
- import StageBadge from './StageBadge.vue'
161
131
 
162
132
  const props = defineProps({
163
133
  projects: { type: Array, default: () => [] },
@@ -181,6 +151,11 @@ function isActive(project) {
181
151
  return props.activeProject?.path === project.path
182
152
  }
183
153
 
154
+ function statusTagType(status) {
155
+ const map = { 'in-progress': 'warning', 'completed': 'success', 'failed': 'error', 'blocked': 'warning' }
156
+ return map[status] || 'default'
157
+ }
158
+
184
159
  function getProjectStatus(project) {
185
160
  const stage = project.state?.currentStage
186
161
  if (!stage) return 'pending'
@@ -0,0 +1,178 @@
1
+ <template>
2
+ <div v-if="project" class="project-overview">
3
+ <!-- Name + Path -->
4
+ <div class="ov-section">
5
+ <span class="ov-name">{{ project.name }}</span>
6
+ <span class="ov-path">{{ shortPath }}</span>
7
+ </div>
8
+ <div class="ov-divider" />
9
+
10
+ <!-- Tech Stack -->
11
+ <div class="ov-section ov-clickable" @click="$emit('show-detail', 'tech')">
12
+ <span class="ov-label">技术栈</span>
13
+ <span v-for="t in overview.techStack" :key="t" class="ov-tag">{{ t }}</span>
14
+ </div>
15
+ <div class="ov-divider" />
16
+
17
+ <!-- Stage -->
18
+ <div v-if="project.state?.currentStage" class="ov-section">
19
+ <span class="ov-label">阶段</span>
20
+ <span class="ov-value">{{ project.state.currentStage }}</span>
21
+ </div>
22
+ <div v-else-if="project.state" class="ov-section">
23
+ <span class="ov-label">阶段</span>
24
+ <span class="ov-value" style="color:#9CA3AF;">未执行</span>
25
+ </div>
26
+ <div v-if="project.state?.currentStage" class="ov-divider" />
27
+
28
+ <!-- Last Active -->
29
+ <div v-if="overview.lastActive" class="ov-section">
30
+ <span class="ov-label">最近活跃</span>
31
+ <span class="ov-value">{{ formatTime(overview.lastActive) }}</span>
32
+ </div>
33
+ <div v-if="overview.lastActive" class="ov-divider" />
34
+
35
+ <!-- Doc Stats -->
36
+ <div v-if="overview.docStats.total > 0" class="ov-section ov-clickable" @click="$emit('show-detail', 'docs')">
37
+ <span class="ov-label">文档</span>
38
+ <span class="ov-value">{{ docSummary }}</span>
39
+ </div>
40
+ <div v-if="overview.docStats.total > 0" class="ov-divider" />
41
+
42
+ <!-- Git -->
43
+ <div class="ov-section ov-git ov-clickable" @click="$emit('show-detail', 'git')">
44
+ <span v-if="overview.git.branch" class="ov-branch">{{ overview.git.branch }}</span>
45
+ <span v-if="overview.git.lastCommit" class="ov-commit" :title="overview.git.lastCommit">{{ truncate(overview.git.lastCommit, 40) }}</span>
46
+ <span v-if="overview.git.dirtyCount > 0" class="ov-dirty">{{ overview.git.dirtyCount }} 未提交</span>
47
+ </div>
48
+ </div>
49
+ </template>
50
+
51
+ <script setup>
52
+ import { computed } from 'vue'
53
+
54
+ const props = defineProps({
55
+ project: { type: Object, default: null }
56
+ })
57
+
58
+ defineEmits(['show-detail'])
59
+
60
+ const overview = computed(() => props.project?.overview || { techStack: [], lastActive: null, docStats: { design: 0, plan: 0, archive: 0, changes: 0, scan: 0, quicklog: 0, total: 0 }, git: { branch: '', lastCommit: '', dirtyCount: 0 } })
61
+
62
+ const shortPath = computed(() => {
63
+ const p = props.project?.path || ''
64
+ return p.replace(/^\/Users\/[^/]+/, '~')
65
+ })
66
+
67
+ const docSummary = computed(() => {
68
+ const s = overview.value.docStats
69
+ const parts = []
70
+ if (s.design) parts.push(`设计 ${s.design}`)
71
+ if (s.plan) parts.push(`计划 ${s.plan}`)
72
+ if (s.archive) parts.push(`归档 ${s.archive}`)
73
+ if (s.changes) parts.push(`变更 ${s.changes}`)
74
+ return parts.join(' / ') || `${s.total} 篇`
75
+ })
76
+
77
+ function formatTime(iso) {
78
+ if (!iso) return ''
79
+ const d = new Date(iso)
80
+ const now = new Date()
81
+ const diffMs = now - d
82
+ if (diffMs < 60000) return '刚刚'
83
+ if (diffMs < 3600000) return `${Math.floor(diffMs / 60000)} 分钟前`
84
+ if (diffMs < 86400000) return `${Math.floor(diffMs / 3600000)} 小时前`
85
+ if (diffMs < 604800000) return `${Math.floor(diffMs / 86400000)} 天前`
86
+ return `${d.getMonth() + 1}/${d.getDate()}`
87
+ }
88
+
89
+ function truncate(s, n) {
90
+ return s.length > n ? s.slice(0, n) + '…' : s
91
+ }
92
+ </script>
93
+
94
+ <style scoped>
95
+ .project-overview {
96
+ display: flex;
97
+ align-items: center;
98
+ background: #FFFFFF;
99
+ border-bottom: 1px solid #E5E5EA;
100
+ padding: 8px 16px;
101
+ min-height: 48px;
102
+ gap: 8px;
103
+ flex-shrink: 0;
104
+ overflow-x: auto;
105
+ }
106
+ .ov-section {
107
+ display: flex;
108
+ align-items: center;
109
+ gap: 6px;
110
+ white-space: nowrap;
111
+ flex-shrink: 0;
112
+ }
113
+ .ov-clickable {
114
+ cursor: pointer;
115
+ border-radius: 4px;
116
+ padding: 2px 6px;
117
+ margin: -2px -6px;
118
+ transition: background 0.15s;
119
+ }
120
+ .ov-clickable:hover {
121
+ background: rgba(217,119,6,0.06);
122
+ }
123
+ .ov-name {
124
+ font-weight: 600;
125
+ font-size: 13px;
126
+ color: #1C1C1E;
127
+ }
128
+ .ov-path {
129
+ font-size: 11px;
130
+ color: #636366;
131
+ }
132
+ .ov-label {
133
+ font-size: 11px;
134
+ color: #636366;
135
+ }
136
+ .ov-value {
137
+ font-size: 12px;
138
+ color: #1C1C1E;
139
+ font-weight: 500;
140
+ }
141
+ .ov-tag {
142
+ font-size: 10px;
143
+ padding: 1px 8px;
144
+ border-radius: 10px;
145
+ background: rgba(217,119,6,0.08);
146
+ color: #D97706;
147
+ font-weight: 500;
148
+ }
149
+ .ov-divider {
150
+ width: 1px;
151
+ height: 20px;
152
+ background: #E5E5EA;
153
+ flex-shrink: 0;
154
+ }
155
+ .ov-git {
156
+ margin-left: auto;
157
+ gap: 10px;
158
+ }
159
+ .ov-branch {
160
+ font-size: 11px;
161
+ color: #636366;
162
+ background: #F5F5F7;
163
+ padding: 1px 8px;
164
+ border-radius: 4px;
165
+ }
166
+ .ov-commit {
167
+ font-size: 11px;
168
+ color: #636366;
169
+ max-width: 200px;
170
+ overflow: hidden;
171
+ text-overflow: ellipsis;
172
+ }
173
+ .ov-dirty {
174
+ font-size: 11px;
175
+ color: #D97706;
176
+ font-weight: 500;
177
+ }
178
+ </style>
@@ -19,19 +19,19 @@ const props = defineProps({
19
19
 
20
20
  const displayLabel = computed(() => {
21
21
  if (props.label) return props.label
22
- const labels = { 'completed': 'done', 'in-progress': 'running', 'blocked': 'blocked', 'failed': 'error', 'pending': 'idle' }
23
- return labels[props.status] || 'idle'
22
+ const labels = { 'completed': 'done', 'in-progress': 'running', 'blocked': 'blocked', 'failed': 'error' }
23
+ return labels[props.status] || ''
24
24
  })
25
25
 
26
26
  const sizeClass = computed(() => props.size === 'sm' ? 'px-1.5 py-0.5' : 'px-2 py-1')
27
27
 
28
28
  const badgeStyle = computed(() => {
29
29
  const styles = {
30
- 'completed': { background: 'rgba(52,211,153,0.1)', color: '#34D399' },
31
- 'in-progress': { background: 'rgba(251,191,36,0.1)', color: '#FBBF24' },
32
- 'blocked': { background: 'rgba(251,146,60,0.1)', color: '#FB923C' },
33
- 'failed': { background: 'rgba(239,68,68,0.1)', color: '#EF4444' },
34
- 'pending': { background: 'rgba(82,82,82,0.15)', color: '#525252' }
30
+ 'completed': { background: 'rgba(22,163,74,0.1)', color: '#16A34A' },
31
+ 'in-progress': { background: 'rgba(251,191,36,0.1)', color: '#D97706' },
32
+ 'blocked': { background: 'rgba(251,146,60,0.1)', color: '#EA580C' },
33
+ 'failed': { background: 'rgba(239,68,68,0.1)', color: '#DC2626' },
34
+ 'pending': { background: 'rgba(82,82,82,0.15)', color: '#6B7280' }
35
35
  }
36
36
  return styles[props.status] || styles.pending
37
37
  })
@@ -42,12 +42,12 @@ const dotClass = computed(() => {
42
42
 
43
43
  const dotStyle = computed(() => {
44
44
  const colors = {
45
- 'completed': '#34D399',
46
- 'in-progress': '#FBBF24',
47
- 'blocked': '#FB923C',
48
- 'failed': '#EF4444',
49
- 'pending': '#525252'
45
+ 'completed': '#16A34A',
46
+ 'in-progress': '#D97706',
47
+ 'blocked': '#EA580C',
48
+ 'failed': '#DC2626',
49
+ 'pending': '#6B7280'
50
50
  }
51
- return { background: colors[props.status] || '#525252' }
51
+ return { background: colors[props.status] || '#6B7280' }
52
52
  })
53
53
  </script>