sillyspec 3.20.3 → 3.20.4

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 (224) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +21 -17
  2. package/.claude/skills/sillyspec-auto/SKILL.md +83 -77
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -17
  4. package/.claude/skills/sillyspec-commit/SKILL.md +106 -105
  5. package/.claude/skills/sillyspec-continue/SKILL.md +45 -44
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +31 -22
  7. package/.claude/skills/sillyspec-execute/SKILL.md +30 -17
  8. package/.claude/skills/sillyspec-explore/SKILL.md +109 -96
  9. package/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
  10. package/.claude/skills/sillyspec-plan/SKILL.md +21 -52
  11. package/.claude/skills/sillyspec-propose/SKILL.md +21 -17
  12. package/.claude/skills/sillyspec-quick/SKILL.md +21 -17
  13. package/.claude/skills/sillyspec-resume/SKILL.md +68 -111
  14. package/.claude/skills/sillyspec-scan/SKILL.md +21 -17
  15. package/.claude/skills/sillyspec-state/SKILL.md +54 -54
  16. package/.claude/skills/sillyspec-status/SKILL.md +21 -17
  17. package/.claude/skills/sillyspec-verify/SKILL.md +21 -17
  18. package/.claude/skills/sillyspec-workspace/SKILL.md +157 -149
  19. package/.husky/pre-push +13 -0
  20. package/CLAUDE.md +18 -0
  21. package/README.md +198 -186
  22. package/SKILL.md +90 -87
  23. package/bin/sillyspec.js +2 -2
  24. package/docs/brainstorm-plan-contract.md +64 -0
  25. package/docs/plan-execute-contract.md +123 -0
  26. package/docs/platform-scan-protocol.md +298 -0
  27. package/docs/revision-mode.md +115 -0
  28. package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -0
  29. package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -0
  30. package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -0
  31. package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -0
  32. package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -0
  33. package/docs/sillyspec/file-lifecycle.md +125 -0
  34. package/docs/workflow-contract-regression.md +106 -0
  35. package/docs/worktree-isolation.md +252 -0
  36. package/package.json +40 -34
  37. package/packages/dashboard/dist/assets/{index-D1EVTLmc.js → index-Bq_Z2hne.js} +7446 -7446
  38. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
  39. package/packages/dashboard/dist/index.html +16 -16
  40. package/packages/dashboard/dist/prototype-dashboard.html +836 -0
  41. package/packages/dashboard/dist/prototype-overview.html +256 -0
  42. package/packages/dashboard/index.html +15 -15
  43. package/packages/dashboard/package-lock.json +2384 -2384
  44. package/packages/dashboard/package.json +25 -25
  45. package/packages/dashboard/public/prototype-dashboard.html +836 -0
  46. package/packages/dashboard/public/prototype-overview.html +256 -0
  47. package/packages/dashboard/server/executor.js +86 -86
  48. package/packages/dashboard/server/index.js +588 -508
  49. package/packages/dashboard/server/parser.js +526 -458
  50. package/packages/dashboard/server/watcher.js +350 -342
  51. package/packages/dashboard/src/App.vue +558 -325
  52. package/packages/dashboard/src/components/ActionBar.vue +93 -84
  53. package/packages/dashboard/src/components/CommandPalette.vue +96 -92
  54. package/packages/dashboard/src/components/DetailPanel.vue +137 -137
  55. package/packages/dashboard/src/components/DocPreview.vue +105 -8
  56. package/packages/dashboard/src/components/DocTree.vue +75 -19
  57. package/packages/dashboard/src/components/HResizeHandle.vue +48 -0
  58. package/packages/dashboard/src/components/LogStream.vue +65 -65
  59. package/packages/dashboard/src/components/PipelineStage.vue +95 -75
  60. package/packages/dashboard/src/components/PipelineView.vue +156 -129
  61. package/packages/dashboard/src/components/ProjectCard.vue +187 -0
  62. package/packages/dashboard/src/components/ProjectList.vue +210 -210
  63. package/packages/dashboard/src/components/ProjectOverview.vue +113 -139
  64. package/packages/dashboard/src/components/StageBadge.vue +67 -53
  65. package/packages/dashboard/src/components/StepCard.vue +94 -89
  66. package/packages/dashboard/src/components/VResizeHandle.vue +61 -0
  67. package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
  68. package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
  69. package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
  70. package/packages/dashboard/src/composables/useDashboard.js +192 -164
  71. package/packages/dashboard/src/composables/useKeyboard.js +119 -119
  72. package/packages/dashboard/src/composables/useLayout.js +131 -0
  73. package/packages/dashboard/src/composables/useWebSocket.js +129 -129
  74. package/packages/dashboard/src/main.js +8 -8
  75. package/packages/dashboard/src/style.css +132 -132
  76. package/packages/dashboard/vite.config.js +18 -18
  77. package/src/brainstorm-postcheck.js +158 -0
  78. package/src/change-list.js +52 -0
  79. package/src/change-risk-profile.js +352 -0
  80. package/src/classify-change.js +73 -0
  81. package/src/constants.js +70 -0
  82. package/src/contract-matrix.js +278 -0
  83. package/src/db.js +201 -0
  84. package/src/endpoint-extractor.js +315 -0
  85. package/src/hooks/claude-pre-tool-use.cjs +125 -0
  86. package/src/hooks/worktree-guard.js +761 -0
  87. package/src/index.js +922 -252
  88. package/src/init.js +431 -375
  89. package/src/knowledge-match.js +130 -0
  90. package/src/migrate.js +117 -117
  91. package/src/modules.js +482 -0
  92. package/src/progress.js +1734 -508
  93. package/src/run.js +3465 -652
  94. package/src/scan-postcheck.js +387 -0
  95. package/src/setup.js +398 -460
  96. package/src/stage-contract.js +700 -0
  97. package/src/stages/archive.js +160 -54
  98. package/src/stages/brainstorm-auto.js +229 -0
  99. package/src/stages/brainstorm.js +645 -239
  100. package/src/stages/doctor.js +365 -312
  101. package/src/stages/execute.js +625 -264
  102. package/src/stages/explore.js +34 -0
  103. package/src/stages/index.js +29 -35
  104. package/src/stages/knowledge.js +498 -0
  105. package/src/stages/plan-postcheck.js +511 -0
  106. package/src/stages/plan.js +582 -279
  107. package/src/stages/propose.js +174 -115
  108. package/src/stages/quick.js +102 -63
  109. package/src/stages/scan.js +558 -141
  110. package/src/stages/status.js +65 -65
  111. package/src/stages/verify.js +322 -135
  112. package/src/sync.js +497 -0
  113. package/src/task-review.js +346 -0
  114. package/src/workflow.js +785 -0
  115. package/src/worktree-apply.js +549 -0
  116. package/src/worktree-deps.js +185 -0
  117. package/src/worktree.js +982 -0
  118. package/templates/workflows/archive-impact.yaml +79 -0
  119. package/templates/workflows/scan-docs.yaml +132 -0
  120. package/test/brainstorm-plan-contract.test.mjs +273 -0
  121. package/test/check-syntax.mjs +26 -0
  122. package/test/cli-top-level-aliases.test.mjs +174 -0
  123. package/test/contract-artifacts.test.mjs +323 -0
  124. package/test/decision-supersede.test.mjs +277 -0
  125. package/test/knowledge-match.test.mjs +231 -0
  126. package/test/plan-execute-contract.test.mjs +330 -0
  127. package/test/plan-optimization.test.mjs +572 -0
  128. package/test/platform-artifacts.test.mjs +190 -0
  129. package/test/platform-failure-samples.test.mjs +199 -0
  130. package/test/platform-recovery-chain.test.mjs +179 -0
  131. package/test/platform-recovery.test.mjs +167 -0
  132. package/test/platform-scan-p0.test.mjs +175 -0
  133. package/test/revision-v1.test.mjs +1145 -0
  134. package/test/run-sanitize-project-name.test.mjs +51 -0
  135. package/test/run-scan-postcheck-fail.test.mjs +64 -0
  136. package/test/run-scan-project-parse.test.mjs +200 -0
  137. package/test/run-tests.mjs +48 -0
  138. package/test/scan-docs-yaml-placeholders.test.mjs +84 -0
  139. package/test/scan-knowledge.test.mjs +175 -0
  140. package/test/scan-paths.test.mjs +68 -0
  141. package/test/scan-postcheck-project-priority.test.mjs +85 -0
  142. package/test/scan-postcheck.test.mjs +197 -0
  143. package/test/scan-workflow-anyfailed-block.test.mjs +52 -0
  144. package/test/spec-dir.test.mjs +206 -0
  145. package/test/stage-contract-failed-post-check.test.mjs +102 -0
  146. package/test/stage-contract.test.mjs +299 -0
  147. package/test/stage-definitions.test.mjs +39 -0
  148. package/test/wait-gates.test.mjs +501 -0
  149. package/test/workflow-spec-base.test.mjs +142 -0
  150. package/test/worktree-deps-provision.test.mjs +148 -0
  151. package/test/worktree-guard.test.mjs +136 -0
  152. package/test/worktree-native-overlay.test.mjs +188 -0
  153. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +0 -97
  154. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +0 -51
  155. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +0 -29
  156. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +0 -34
  157. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +0 -13
  158. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +0 -43
  159. package/.sillyspec/changes/auto-mode/design.md +0 -50
  160. package/.sillyspec/changes/auto-mode/proposal.md +0 -19
  161. package/.sillyspec/changes/auto-mode/requirements.md +0 -21
  162. package/.sillyspec/changes/auto-mode/tasks.md +0 -7
  163. package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +0 -206
  164. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +0 -199
  165. package/.sillyspec/changes/dashboard/design.md +0 -219
  166. package/.sillyspec/changes/dashboard/design.md.braindraft +0 -206
  167. package/.sillyspec/changes/run-command-design/design.md +0 -1230
  168. package/.sillyspec/changes/unified-docs-design/design.md +0 -199
  169. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  170. package/.sillyspec/knowledge/INDEX.md +0 -8
  171. package/.sillyspec/knowledge/uncategorized.md +0 -3
  172. package/.sillyspec/plans/2026-04-05-dashboard.md +0 -737
  173. package/.sillyspec/projects/sillyspec.yaml +0 -3
  174. package/dist/steps/brainstorm/01-load-context.md +0 -30
  175. package/dist/steps/brainstorm/02-reuse-check.md +0 -6
  176. package/dist/steps/brainstorm/03-prototype-analysis.md +0 -11
  177. package/dist/steps/brainstorm/04-module-split.md +0 -23
  178. package/dist/steps/brainstorm/05-dialog-explore.md +0 -8
  179. package/dist/steps/brainstorm/06-propose-approaches.md +0 -3
  180. package/dist/steps/brainstorm/07-present-design.md +0 -3
  181. package/dist/steps/brainstorm/08-write-design.md +0 -21
  182. package/dist/steps/brainstorm/09-self-review.md +0 -15
  183. package/dist/steps/brainstorm/10-user-confirm.md +0 -3
  184. package/dist/steps/brainstorm/11-output-spec.md +0 -7
  185. package/dist/steps/brainstorm/manifest.yaml +0 -26
  186. package/dist/steps/execute/01-load-context.md +0 -41
  187. package/dist/steps/execute/02-scan-conventions.md +0 -47
  188. package/dist/steps/execute/03-skill-mcp.md +0 -19
  189. package/dist/steps/execute/04-assign-task.md +0 -22
  190. package/dist/steps/execute/04b-prompt-template.md +0 -54
  191. package/dist/steps/execute/05-write-test.md +0 -7
  192. package/dist/steps/execute/06-write-code.md +0 -8
  193. package/dist/steps/execute/07-run-test.md +0 -26
  194. package/dist/steps/execute/08-fix-issues.md +0 -28
  195. package/dist/steps/execute/09-next-task.md +0 -33
  196. package/dist/steps/execute/manifest.yaml +0 -28
  197. package/dist/steps/plan/01-load-context.md +0 -22
  198. package/dist/steps/plan/02-anchor-confirm.md +0 -1
  199. package/dist/steps/plan/03-expand-tasks.md +0 -33
  200. package/dist/steps/plan/04-mark-order.md +0 -15
  201. package/dist/steps/plan/05-e2e-planning.md +0 -17
  202. package/dist/steps/plan/06-self-check.md +0 -16
  203. package/dist/steps/plan/07-save.md +0 -1
  204. package/dist/steps/plan/manifest.yaml +0 -18
  205. package/dist/steps/scan/01-env-detect.md +0 -51
  206. package/dist/steps/scan/02-tech-stack.md +0 -16
  207. package/dist/steps/scan/03-conventions.md +0 -16
  208. package/dist/steps/scan/04-structure.md +0 -19
  209. package/dist/steps/scan/05-quality.md +0 -18
  210. package/dist/steps/scan/06-complete.md +0 -49
  211. package/dist/steps/scan/manifest.yaml +0 -16
  212. package/dist/steps/verify/01-load-specs.md +0 -28
  213. package/dist/steps/verify/02-check-tasks.md +0 -1
  214. package/dist/steps/verify/03-check-design.md +0 -6
  215. package/dist/steps/verify/04-run-tests.md +0 -7
  216. package/dist/steps/verify/05-e2e-tests.md +0 -27
  217. package/dist/steps/verify/05b-e2e-fix.md +0 -33
  218. package/dist/steps/verify/06-code-quality.md +0 -25
  219. package/dist/steps/verify/07-lint-check.md +0 -27
  220. package/dist/steps/verify/08-output-report.md +0 -14
  221. package/dist/steps/verify/manifest.yaml +0 -22
  222. package/packages/dashboard/dist/assets/index-DGe8CqeP.css +0 -1
  223. package/src/derive.js +0 -147
  224. package/src/step.js +0 -543
@@ -0,0 +1,131 @@
1
+ /**
2
+ * 布局管理 Composable
3
+ * 管理双层布局的概览/详情比例和三栏宽度
4
+ */
5
+ import { reactive, ref, onMounted } from 'vue'
6
+
7
+ const STORAGE_KEY = 'dashboard-layout-v2'
8
+
9
+ // 默认值
10
+ const DEFAULT_OVERVIEW_HEIGHT = 30 // 概览区域百分比
11
+ const DEFAULT_COLUMN_WIDTHS = [33.33, 33.33, 33.33] // 三栏宽度百分比
12
+
13
+ // 最小值限制
14
+ const MIN_OVERVIEW_HEIGHT = 15
15
+ const MAX_OVERVIEW_HEIGHT = 75
16
+ const MIN_COLUMN_WIDTH = 10
17
+
18
+ /**
19
+ * 布局管理 Hook
20
+ */
21
+ export function useLayout() {
22
+ // 布局状态
23
+ const layout = reactive({
24
+ overviewHeight: DEFAULT_OVERVIEW_HEIGHT,
25
+ columnWidths: [...DEFAULT_COLUMN_WIDTHS]
26
+ })
27
+
28
+ // 拖动状态
29
+ const isDragging = ref(false)
30
+ const dragType = ref(null) // 'vertical' | 'horizontal'
31
+
32
+ /**
33
+ * 加载保存的布局
34
+ */
35
+ function loadLayout() {
36
+ try {
37
+ const saved = localStorage.getItem(STORAGE_KEY)
38
+ if (saved) {
39
+ const parsed = JSON.parse(saved)
40
+ if (parsed.overviewHeight !== undefined) {
41
+ layout.overviewHeight = Math.max(MIN_OVERVIEW_HEIGHT, Math.min(MAX_OVERVIEW_HEIGHT, parsed.overviewHeight))
42
+ }
43
+ if (Array.isArray(parsed.columnWidths) && parsed.columnWidths.length === 3) {
44
+ layout.columnWidths = parsed.columnWidths
45
+ }
46
+ }
47
+ } catch (e) {
48
+ console.error('Failed to load layout:', e)
49
+ // 使用默认值
50
+ resetLayout()
51
+ }
52
+ }
53
+
54
+ /**
55
+ * 保存布局到 localStorage
56
+ */
57
+ function saveLayout() {
58
+ try {
59
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({
60
+ overviewHeight: layout.overviewHeight,
61
+ columnWidths: layout.columnWidths
62
+ }))
63
+ } catch (e) {
64
+ console.error('Failed to save layout:', e)
65
+ }
66
+ }
67
+
68
+ /**
69
+ * 重置布局为默认值
70
+ */
71
+ function resetLayout() {
72
+ layout.overviewHeight = DEFAULT_OVERVIEW_HEIGHT
73
+ layout.columnWidths = [...DEFAULT_COLUMN_WIDTHS]
74
+ saveLayout()
75
+ }
76
+
77
+ /**
78
+ * 设置概览区域高度(百分比)
79
+ */
80
+ function setOverviewHeight(percent) {
81
+ layout.overviewHeight = Math.max(MIN_OVERVIEW_HEIGHT, Math.min(MAX_OVERVIEW_HEIGHT, percent))
82
+ saveLayout()
83
+ }
84
+
85
+ /**
86
+ * 设置列宽度(索引,百分比)
87
+ */
88
+ function setColumnWidth(index, percent) {
89
+ if (index >= 0 && index < 3) {
90
+ layout.columnWidths[index] = Math.max(MIN_COLUMN_WIDTH, percent)
91
+ saveLayout()
92
+ }
93
+ }
94
+
95
+ /**
96
+ * 开始拖动
97
+ */
98
+ function startDrag(type) {
99
+ isDragging.value = true
100
+ dragType.value = type
101
+ document.body.classList.add('resizing')
102
+ }
103
+
104
+ /**
105
+ * 结束拖动
106
+ */
107
+ function endDrag() {
108
+ isDragging.value = false
109
+ dragType.value = null
110
+ document.body.classList.remove('resizing')
111
+ saveLayout()
112
+ }
113
+
114
+ // 组件挂载时加载布局
115
+ onMounted(() => {
116
+ loadLayout()
117
+ })
118
+
119
+ return {
120
+ layout,
121
+ isDragging,
122
+ dragType,
123
+ loadLayout,
124
+ saveLayout,
125
+ resetLayout,
126
+ setOverviewHeight,
127
+ setColumnWidth,
128
+ startDrag,
129
+ endDrag
130
+ }
131
+ }
@@ -1,129 +1,129 @@
1
- import { ref, onMounted, onUnmounted } from 'vue'
2
-
3
- /**
4
- * WebSocket connection composable with auto-reconnect
5
- * @returns {object} WebSocket API
6
- */
7
- export function useWebSocket() {
8
- const ws = ref(null)
9
- const connected = ref(false)
10
- const reconnectTimeout = ref(null)
11
- const handlers = new Map()
12
-
13
- /**
14
- * Connect to WebSocket server
15
- */
16
- function connect() {
17
- const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
18
- const wsUrl = `${protocol}//${location.host}`
19
-
20
- ws.value = new WebSocket(wsUrl)
21
-
22
- ws.value.onopen = () => {
23
- connected.value = true
24
- console.log('WebSocket connected')
25
- }
26
-
27
- ws.value.onclose = () => {
28
- connected.value = false
29
- console.log('WebSocket disconnected, reconnecting in 3s...')
30
-
31
- // Auto-reconnect after 3 seconds
32
- reconnectTimeout.value = setTimeout(() => {
33
- connect()
34
- }, 3000)
35
- }
36
-
37
- ws.value.onerror = (err) => {
38
- console.error('WebSocket error:', err)
39
- }
40
-
41
- ws.value.onmessage = (event) => {
42
- try {
43
- const message = JSON.parse(event.data)
44
- const { type, data } = message
45
-
46
- // Call registered handlers for this message type
47
- const typeHandlers = handlers.get(type) || []
48
- typeHandlers.forEach(handler => handler(data))
49
-
50
- // Call wildcard handlers
51
- const wildcardHandlers = handlers.get('*') || []
52
- wildcardHandlers.forEach(handler => handler(message))
53
- } catch (err) {
54
- console.error('Error parsing WebSocket message:', err)
55
- }
56
- }
57
- }
58
-
59
- /**
60
- * Register event handler for a message type
61
- * @param {string} type - Message type
62
- * @param {function} handler - Handler function
63
- * @returns {function} Unsubscribe function
64
- */
65
- function on(type, handler) {
66
- if (!handlers.has(type)) {
67
- handlers.set(type, [])
68
- }
69
- handlers.get(type).push(handler)
70
-
71
- // Return unsubscribe function
72
- return () => {
73
- const typeHandlers = handlers.get(type)
74
- if (typeHandlers) {
75
- const index = typeHandlers.indexOf(handler)
76
- if (index > -1) {
77
- typeHandlers.splice(index, 1)
78
- }
79
- }
80
- }
81
- }
82
-
83
- /**
84
- * Send message to server
85
- * @param {object} data - Message data
86
- */
87
- function send(data) {
88
- if (ws.value && connected.value) {
89
- ws.value.send(JSON.stringify(data))
90
- } else {
91
- console.warn('Cannot send message: WebSocket not connected')
92
- }
93
- }
94
-
95
- /**
96
- * Disconnect WebSocket
97
- */
98
- function disconnect() {
99
- if (reconnectTimeout.value) {
100
- clearTimeout(reconnectTimeout.value)
101
- reconnectTimeout.value = null
102
- }
103
-
104
- if (ws.value) {
105
- ws.value.close()
106
- ws.value = null
107
- }
108
-
109
- connected.value = false
110
- }
111
-
112
- // Auto-connect on mount
113
- onMounted(() => {
114
- connect()
115
- })
116
-
117
- // Cleanup on unmount
118
- onUnmounted(() => {
119
- disconnect()
120
- })
121
-
122
- return {
123
- connected,
124
- on,
125
- send,
126
- disconnect,
127
- reconnect: connect
128
- }
129
- }
1
+ import { ref, onMounted, onUnmounted } from 'vue'
2
+
3
+ /**
4
+ * WebSocket connection composable with auto-reconnect
5
+ * @returns {object} WebSocket API
6
+ */
7
+ export function useWebSocket() {
8
+ const ws = ref(null)
9
+ const connected = ref(false)
10
+ const reconnectTimeout = ref(null)
11
+ const handlers = new Map()
12
+
13
+ /**
14
+ * Connect to WebSocket server
15
+ */
16
+ function connect() {
17
+ const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
18
+ const wsUrl = `${protocol}//${location.host}`
19
+
20
+ ws.value = new WebSocket(wsUrl)
21
+
22
+ ws.value.onopen = () => {
23
+ connected.value = true
24
+ console.log('WebSocket connected')
25
+ }
26
+
27
+ ws.value.onclose = () => {
28
+ connected.value = false
29
+ console.log('WebSocket disconnected, reconnecting in 3s...')
30
+
31
+ // Auto-reconnect after 3 seconds
32
+ reconnectTimeout.value = setTimeout(() => {
33
+ connect()
34
+ }, 3000)
35
+ }
36
+
37
+ ws.value.onerror = (err) => {
38
+ console.error('WebSocket error:', err)
39
+ }
40
+
41
+ ws.value.onmessage = (event) => {
42
+ try {
43
+ const message = JSON.parse(event.data)
44
+ const { type, data } = message
45
+
46
+ // Call registered handlers for this message type
47
+ const typeHandlers = handlers.get(type) || []
48
+ typeHandlers.forEach(handler => handler(data))
49
+
50
+ // Call wildcard handlers
51
+ const wildcardHandlers = handlers.get('*') || []
52
+ wildcardHandlers.forEach(handler => handler(message))
53
+ } catch (err) {
54
+ console.error('Error parsing WebSocket message:', err)
55
+ }
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Register event handler for a message type
61
+ * @param {string} type - Message type
62
+ * @param {function} handler - Handler function
63
+ * @returns {function} Unsubscribe function
64
+ */
65
+ function on(type, handler) {
66
+ if (!handlers.has(type)) {
67
+ handlers.set(type, [])
68
+ }
69
+ handlers.get(type).push(handler)
70
+
71
+ // Return unsubscribe function
72
+ return () => {
73
+ const typeHandlers = handlers.get(type)
74
+ if (typeHandlers) {
75
+ const index = typeHandlers.indexOf(handler)
76
+ if (index > -1) {
77
+ typeHandlers.splice(index, 1)
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Send message to server
85
+ * @param {object} data - Message data
86
+ */
87
+ function send(data) {
88
+ if (ws.value && connected.value) {
89
+ ws.value.send(JSON.stringify(data))
90
+ } else {
91
+ console.warn('Cannot send message: WebSocket not connected')
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Disconnect WebSocket
97
+ */
98
+ function disconnect() {
99
+ if (reconnectTimeout.value) {
100
+ clearTimeout(reconnectTimeout.value)
101
+ reconnectTimeout.value = null
102
+ }
103
+
104
+ if (ws.value) {
105
+ ws.value.close()
106
+ ws.value = null
107
+ }
108
+
109
+ connected.value = false
110
+ }
111
+
112
+ // Auto-connect on mount
113
+ onMounted(() => {
114
+ connect()
115
+ })
116
+
117
+ // Cleanup on unmount
118
+ onUnmounted(() => {
119
+ disconnect()
120
+ })
121
+
122
+ return {
123
+ connected,
124
+ on,
125
+ send,
126
+ disconnect,
127
+ reconnect: connect
128
+ }
129
+ }
@@ -1,8 +1,8 @@
1
- import { createApp } from 'vue'
2
- import naive from 'naive-ui'
3
- import App from './App.vue'
4
- import './style.css'
5
-
6
- const app = createApp(App)
7
- app.use(naive)
8
- app.mount('#app')
1
+ import { createApp } from 'vue'
2
+ import naive from 'naive-ui'
3
+ import App from './App.vue'
4
+ import './style.css'
5
+
6
+ const app = createApp(App)
7
+ app.use(naive)
8
+ app.mount('#app')
@@ -1,132 +1,132 @@
1
- @import "tailwindcss";
2
-
3
- @theme {
4
- --color-bg: #F5F5F7;
5
- --color-primary: #D97706;
6
- --color-primary-dim: #92400E;
7
- --color-warning: #EA580C;
8
- --color-danger: #DC2626;
9
- --color-muted: #6B7280;
10
- --color-surface: #FFFFFF;
11
- --color-surface-2: #F0F0F3;
12
- --color-border: #E5E5EA;
13
- --color-border-light: #D1D1D6;
14
- --color-text: #1C1C1E;
15
- --color-text-secondary: #636366;
16
- --color-info: #2563EB;
17
- --color-success: #16A34A;
18
-
19
- --font-display: 'JetBrains Mono', monospace;
20
- --font-body: 'DM Sans', sans-serif;
21
-
22
- --animate-pulse-glow: pulse-glow 2s ease-in-out infinite;
23
- --animate-pulse-dot: pulse-dot 1.5s ease-in-out infinite;
24
- --animate-breathe: breathe 3s ease-in-out infinite;
25
- --animate-shimmer: shimmer 2s linear infinite;
26
- --animate-slide-in: slide-in 300ms cubic-bezier(0.16, 1, 0.3, 1);
27
- }
28
-
29
- @keyframes pulse-glow {
30
- 0%, 100% { box-shadow: 0 0 0 0 rgba(251, 191, 36, 0.4); }
31
- 50% { box-shadow: 0 0 12px 2px rgba(251, 191, 36, 0.15); }
32
- }
33
-
34
- @keyframes pulse-dot {
35
- 0%, 100% { opacity: 1; transform: scale(1); }
36
- 50% { opacity: 0.4; transform: scale(1.5); }
37
- }
38
-
39
- @keyframes breathe {
40
- 0%, 100% { opacity: 0.5; }
41
- 50% { opacity: 1; }
42
- }
43
-
44
- @keyframes shimmer {
45
- 0% { background-position: -200% 0; }
46
- 100% { background-position: 200% 0; }
47
- }
48
-
49
- @keyframes slide-in {
50
- from { opacity: 0; transform: translateY(6px); }
51
- to { opacity: 1; transform: translateY(0); }
52
- }
53
-
54
- @keyframes glow-border {
55
- 0%, 100% { border-color: rgba(251, 191, 36, 0.15); }
56
- 50% { border-color: rgba(251, 191, 36, 0.5); }
57
- }
58
-
59
- @keyframes float {
60
- 0%, 100% { transform: translateY(0px); }
61
- 50% { transform: translateY(-3px); }
62
- }
63
-
64
- .font-mono-log {
65
- font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
66
- }
67
-
68
- /* Noise texture background */
69
- .noise-bg {
70
- position: relative;
71
- }
72
- .noise-bg::before {
73
- content: '';
74
- position: absolute;
75
- inset: 0;
76
- background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.03'/%3E%3C/svg%3E");
77
- pointer-events: none;
78
- z-index: 0;
79
- }
80
-
81
- /* Diagonal accent stripe */
82
- .accent-stripe {
83
- background: repeating-linear-gradient(
84
- -45deg,
85
- transparent,
86
- transparent 8px,
87
- rgba(251, 191, 36, 0.03) 8px,
88
- rgba(251, 191, 36, 0.03) 9px
89
- );
90
- }
91
-
92
- /* Progress bar */
93
- .progress-gradient {
94
- background: linear-gradient(90deg, #FBBF24, #F59E0B, #FB923C);
95
- }
96
-
97
- /* Skeleton shimmer */
98
- .skeleton-shimmer {
99
- background: linear-gradient(90deg, #1A1E28 25%, #2A3040 50%, #1A1E28 75%);
100
- background-size: 200% 100%;
101
- animation: shimmer 1.5s ease-in-out infinite;
102
- }
103
-
104
- /* Scrollbar */
105
- ::-webkit-scrollbar {
106
- width: 5px;
107
- height: 5px;
108
- }
109
-
110
- ::-webkit-scrollbar-track {
111
- background: transparent;
112
- }
113
-
114
- ::-webkit-scrollbar-thumb {
115
- background: #2A3040;
116
- border-radius: 10px;
117
- }
118
-
119
- ::-webkit-scrollbar-thumb:hover {
120
- background: #3A4555;
121
- }
122
-
123
- * {
124
- scrollbar-width: thin;
125
- scrollbar-color: #2A2A2D transparent;
126
- }
127
-
128
- /* Selection */
129
- ::selection {
130
- background: rgba(251, 191, 36, 0.2);
131
- color: #FBBF24;
132
- }
1
+ @import "tailwindcss";
2
+
3
+ @theme {
4
+ --color-bg: #F5F5F7;
5
+ --color-primary: #D97706;
6
+ --color-primary-dim: #92400E;
7
+ --color-warning: #EA580C;
8
+ --color-danger: #DC2626;
9
+ --color-muted: #6B7280;
10
+ --color-surface: #FFFFFF;
11
+ --color-surface-2: #F0F0F3;
12
+ --color-border: #E5E5EA;
13
+ --color-border-light: #D1D1D6;
14
+ --color-text: #1C1C1E;
15
+ --color-text-secondary: #636366;
16
+ --color-info: #2563EB;
17
+ --color-success: #16A34A;
18
+
19
+ --font-display: 'JetBrains Mono', monospace;
20
+ --font-body: 'DM Sans', sans-serif;
21
+
22
+ --animate-pulse-glow: pulse-glow 2s ease-in-out infinite;
23
+ --animate-pulse-dot: pulse-dot 1.5s ease-in-out infinite;
24
+ --animate-breathe: breathe 3s ease-in-out infinite;
25
+ --animate-shimmer: shimmer 2s linear infinite;
26
+ --animate-slide-in: slide-in 300ms cubic-bezier(0.16, 1, 0.3, 1);
27
+ }
28
+
29
+ @keyframes pulse-glow {
30
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(251, 191, 36, 0.4); }
31
+ 50% { box-shadow: 0 0 12px 2px rgba(251, 191, 36, 0.15); }
32
+ }
33
+
34
+ @keyframes pulse-dot {
35
+ 0%, 100% { opacity: 1; transform: scale(1); }
36
+ 50% { opacity: 0.4; transform: scale(1.5); }
37
+ }
38
+
39
+ @keyframes breathe {
40
+ 0%, 100% { opacity: 0.5; }
41
+ 50% { opacity: 1; }
42
+ }
43
+
44
+ @keyframes shimmer {
45
+ 0% { background-position: -200% 0; }
46
+ 100% { background-position: 200% 0; }
47
+ }
48
+
49
+ @keyframes slide-in {
50
+ from { opacity: 0; transform: translateY(6px); }
51
+ to { opacity: 1; transform: translateY(0); }
52
+ }
53
+
54
+ @keyframes glow-border {
55
+ 0%, 100% { border-color: rgba(251, 191, 36, 0.15); }
56
+ 50% { border-color: rgba(251, 191, 36, 0.5); }
57
+ }
58
+
59
+ @keyframes float {
60
+ 0%, 100% { transform: translateY(0px); }
61
+ 50% { transform: translateY(-3px); }
62
+ }
63
+
64
+ .font-mono-log {
65
+ font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
66
+ }
67
+
68
+ /* Noise texture background */
69
+ .noise-bg {
70
+ position: relative;
71
+ }
72
+ .noise-bg::before {
73
+ content: '';
74
+ position: absolute;
75
+ inset: 0;
76
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.03'/%3E%3C/svg%3E");
77
+ pointer-events: none;
78
+ z-index: 0;
79
+ }
80
+
81
+ /* Diagonal accent stripe */
82
+ .accent-stripe {
83
+ background: repeating-linear-gradient(
84
+ -45deg,
85
+ transparent,
86
+ transparent 8px,
87
+ rgba(251, 191, 36, 0.03) 8px,
88
+ rgba(251, 191, 36, 0.03) 9px
89
+ );
90
+ }
91
+
92
+ /* Progress bar */
93
+ .progress-gradient {
94
+ background: linear-gradient(90deg, #FBBF24, #F59E0B, #FB923C);
95
+ }
96
+
97
+ /* Skeleton shimmer */
98
+ .skeleton-shimmer {
99
+ background: linear-gradient(90deg, #1A1E28 25%, #2A3040 50%, #1A1E28 75%);
100
+ background-size: 200% 100%;
101
+ animation: shimmer 1.5s ease-in-out infinite;
102
+ }
103
+
104
+ /* Scrollbar */
105
+ ::-webkit-scrollbar {
106
+ width: 5px;
107
+ height: 5px;
108
+ }
109
+
110
+ ::-webkit-scrollbar-track {
111
+ background: transparent;
112
+ }
113
+
114
+ ::-webkit-scrollbar-thumb {
115
+ background: #2A3040;
116
+ border-radius: 10px;
117
+ }
118
+
119
+ ::-webkit-scrollbar-thumb:hover {
120
+ background: #3A4555;
121
+ }
122
+
123
+ * {
124
+ scrollbar-width: thin;
125
+ scrollbar-color: #2A2A2D transparent;
126
+ }
127
+
128
+ /* Selection */
129
+ ::selection {
130
+ background: rgba(251, 191, 36, 0.2);
131
+ color: #FBBF24;
132
+ }