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.
- package/.claude/skills/sillyspec-archive/SKILL.md +21 -17
- package/.claude/skills/sillyspec-auto/SKILL.md +83 -77
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -17
- package/.claude/skills/sillyspec-commit/SKILL.md +106 -105
- package/.claude/skills/sillyspec-continue/SKILL.md +45 -44
- package/.claude/skills/sillyspec-doctor/SKILL.md +31 -22
- package/.claude/skills/sillyspec-execute/SKILL.md +30 -17
- package/.claude/skills/sillyspec-explore/SKILL.md +109 -96
- package/.claude/skills/sillyspec-knowledge/SKILL.md +270 -0
- package/.claude/skills/sillyspec-plan/SKILL.md +21 -52
- package/.claude/skills/sillyspec-propose/SKILL.md +21 -17
- package/.claude/skills/sillyspec-quick/SKILL.md +21 -17
- package/.claude/skills/sillyspec-resume/SKILL.md +68 -111
- package/.claude/skills/sillyspec-scan/SKILL.md +21 -17
- package/.claude/skills/sillyspec-state/SKILL.md +54 -54
- package/.claude/skills/sillyspec-status/SKILL.md +21 -17
- package/.claude/skills/sillyspec-verify/SKILL.md +21 -17
- package/.claude/skills/sillyspec-workspace/SKILL.md +157 -149
- package/.husky/pre-push +13 -0
- package/CLAUDE.md +18 -0
- package/README.md +198 -186
- package/SKILL.md +90 -87
- package/bin/sillyspec.js +2 -2
- package/docs/brainstorm-plan-contract.md +64 -0
- package/docs/plan-execute-contract.md +123 -0
- package/docs/platform-scan-protocol.md +298 -0
- package/docs/revision-mode.md +115 -0
- package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -0
- package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -0
- package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -0
- package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -0
- package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -0
- package/docs/sillyspec/file-lifecycle.md +125 -0
- package/docs/workflow-contract-regression.md +106 -0
- package/docs/worktree-isolation.md +252 -0
- package/package.json +40 -34
- package/packages/dashboard/dist/assets/{index-D1EVTLmc.js → index-Bq_Z2hne.js} +7446 -7446
- package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -0
- package/packages/dashboard/dist/index.html +16 -16
- package/packages/dashboard/dist/prototype-dashboard.html +836 -0
- package/packages/dashboard/dist/prototype-overview.html +256 -0
- package/packages/dashboard/index.html +15 -15
- package/packages/dashboard/package-lock.json +2384 -2384
- package/packages/dashboard/package.json +25 -25
- package/packages/dashboard/public/prototype-dashboard.html +836 -0
- package/packages/dashboard/public/prototype-overview.html +256 -0
- package/packages/dashboard/server/executor.js +86 -86
- package/packages/dashboard/server/index.js +588 -508
- package/packages/dashboard/server/parser.js +526 -458
- package/packages/dashboard/server/watcher.js +350 -342
- package/packages/dashboard/src/App.vue +558 -325
- package/packages/dashboard/src/components/ActionBar.vue +93 -84
- package/packages/dashboard/src/components/CommandPalette.vue +96 -92
- package/packages/dashboard/src/components/DetailPanel.vue +137 -137
- package/packages/dashboard/src/components/DocPreview.vue +105 -8
- package/packages/dashboard/src/components/DocTree.vue +75 -19
- package/packages/dashboard/src/components/HResizeHandle.vue +48 -0
- package/packages/dashboard/src/components/LogStream.vue +65 -65
- package/packages/dashboard/src/components/PipelineStage.vue +95 -75
- package/packages/dashboard/src/components/PipelineView.vue +156 -129
- package/packages/dashboard/src/components/ProjectCard.vue +187 -0
- package/packages/dashboard/src/components/ProjectList.vue +210 -210
- package/packages/dashboard/src/components/ProjectOverview.vue +113 -139
- package/packages/dashboard/src/components/StageBadge.vue +67 -53
- package/packages/dashboard/src/components/StepCard.vue +94 -89
- package/packages/dashboard/src/components/VResizeHandle.vue +61 -0
- package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
- package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
- package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
- package/packages/dashboard/src/composables/useDashboard.js +192 -164
- package/packages/dashboard/src/composables/useKeyboard.js +119 -119
- package/packages/dashboard/src/composables/useLayout.js +131 -0
- package/packages/dashboard/src/composables/useWebSocket.js +129 -129
- package/packages/dashboard/src/main.js +8 -8
- package/packages/dashboard/src/style.css +132 -132
- package/packages/dashboard/vite.config.js +18 -18
- package/src/brainstorm-postcheck.js +158 -0
- package/src/change-list.js +52 -0
- package/src/change-risk-profile.js +352 -0
- package/src/classify-change.js +73 -0
- package/src/constants.js +70 -0
- package/src/contract-matrix.js +278 -0
- package/src/db.js +201 -0
- package/src/endpoint-extractor.js +315 -0
- package/src/hooks/claude-pre-tool-use.cjs +125 -0
- package/src/hooks/worktree-guard.js +761 -0
- package/src/index.js +922 -252
- package/src/init.js +431 -375
- package/src/knowledge-match.js +130 -0
- package/src/migrate.js +117 -117
- package/src/modules.js +482 -0
- package/src/progress.js +1734 -508
- package/src/run.js +3465 -652
- package/src/scan-postcheck.js +387 -0
- package/src/setup.js +398 -460
- package/src/stage-contract.js +700 -0
- package/src/stages/archive.js +160 -54
- package/src/stages/brainstorm-auto.js +229 -0
- package/src/stages/brainstorm.js +645 -239
- package/src/stages/doctor.js +365 -312
- package/src/stages/execute.js +625 -264
- package/src/stages/explore.js +34 -0
- package/src/stages/index.js +29 -35
- package/src/stages/knowledge.js +498 -0
- package/src/stages/plan-postcheck.js +511 -0
- package/src/stages/plan.js +582 -279
- package/src/stages/propose.js +174 -115
- package/src/stages/quick.js +102 -63
- package/src/stages/scan.js +558 -141
- package/src/stages/status.js +65 -65
- package/src/stages/verify.js +322 -135
- package/src/sync.js +497 -0
- package/src/task-review.js +346 -0
- package/src/workflow.js +785 -0
- package/src/worktree-apply.js +549 -0
- package/src/worktree-deps.js +185 -0
- package/src/worktree.js +982 -0
- package/templates/workflows/archive-impact.yaml +79 -0
- package/templates/workflows/scan-docs.yaml +132 -0
- package/test/brainstorm-plan-contract.test.mjs +273 -0
- package/test/check-syntax.mjs +26 -0
- package/test/cli-top-level-aliases.test.mjs +174 -0
- package/test/contract-artifacts.test.mjs +323 -0
- package/test/decision-supersede.test.mjs +277 -0
- package/test/knowledge-match.test.mjs +231 -0
- package/test/plan-execute-contract.test.mjs +330 -0
- package/test/plan-optimization.test.mjs +572 -0
- package/test/platform-artifacts.test.mjs +190 -0
- package/test/platform-failure-samples.test.mjs +199 -0
- package/test/platform-recovery-chain.test.mjs +179 -0
- package/test/platform-recovery.test.mjs +167 -0
- package/test/platform-scan-p0.test.mjs +175 -0
- package/test/revision-v1.test.mjs +1145 -0
- package/test/run-sanitize-project-name.test.mjs +51 -0
- package/test/run-scan-postcheck-fail.test.mjs +64 -0
- package/test/run-scan-project-parse.test.mjs +200 -0
- package/test/run-tests.mjs +48 -0
- package/test/scan-docs-yaml-placeholders.test.mjs +84 -0
- package/test/scan-knowledge.test.mjs +175 -0
- package/test/scan-paths.test.mjs +68 -0
- package/test/scan-postcheck-project-priority.test.mjs +85 -0
- package/test/scan-postcheck.test.mjs +197 -0
- package/test/scan-workflow-anyfailed-block.test.mjs +52 -0
- package/test/spec-dir.test.mjs +206 -0
- package/test/stage-contract-failed-post-check.test.mjs +102 -0
- package/test/stage-contract.test.mjs +299 -0
- package/test/stage-definitions.test.mjs +39 -0
- package/test/wait-gates.test.mjs +501 -0
- package/test/workflow-spec-base.test.mjs +142 -0
- package/test/worktree-deps-provision.test.mjs +148 -0
- package/test/worktree-guard.test.mjs +136 -0
- package/test/worktree-native-overlay.test.mjs +188 -0
- package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +0 -97
- package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +0 -51
- package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +0 -29
- package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +0 -34
- package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +0 -13
- package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +0 -43
- package/.sillyspec/changes/auto-mode/design.md +0 -50
- package/.sillyspec/changes/auto-mode/proposal.md +0 -19
- package/.sillyspec/changes/auto-mode/requirements.md +0 -21
- package/.sillyspec/changes/auto-mode/tasks.md +0 -7
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +0 -206
- package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +0 -199
- package/.sillyspec/changes/dashboard/design.md +0 -219
- package/.sillyspec/changes/dashboard/design.md.braindraft +0 -206
- package/.sillyspec/changes/run-command-design/design.md +0 -1230
- package/.sillyspec/changes/unified-docs-design/design.md +0 -199
- package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
- package/.sillyspec/knowledge/INDEX.md +0 -8
- package/.sillyspec/knowledge/uncategorized.md +0 -3
- package/.sillyspec/plans/2026-04-05-dashboard.md +0 -737
- package/.sillyspec/projects/sillyspec.yaml +0 -3
- package/dist/steps/brainstorm/01-load-context.md +0 -30
- package/dist/steps/brainstorm/02-reuse-check.md +0 -6
- package/dist/steps/brainstorm/03-prototype-analysis.md +0 -11
- package/dist/steps/brainstorm/04-module-split.md +0 -23
- package/dist/steps/brainstorm/05-dialog-explore.md +0 -8
- package/dist/steps/brainstorm/06-propose-approaches.md +0 -3
- package/dist/steps/brainstorm/07-present-design.md +0 -3
- package/dist/steps/brainstorm/08-write-design.md +0 -21
- package/dist/steps/brainstorm/09-self-review.md +0 -15
- package/dist/steps/brainstorm/10-user-confirm.md +0 -3
- package/dist/steps/brainstorm/11-output-spec.md +0 -7
- package/dist/steps/brainstorm/manifest.yaml +0 -26
- package/dist/steps/execute/01-load-context.md +0 -41
- package/dist/steps/execute/02-scan-conventions.md +0 -47
- package/dist/steps/execute/03-skill-mcp.md +0 -19
- package/dist/steps/execute/04-assign-task.md +0 -22
- package/dist/steps/execute/04b-prompt-template.md +0 -54
- package/dist/steps/execute/05-write-test.md +0 -7
- package/dist/steps/execute/06-write-code.md +0 -8
- package/dist/steps/execute/07-run-test.md +0 -26
- package/dist/steps/execute/08-fix-issues.md +0 -28
- package/dist/steps/execute/09-next-task.md +0 -33
- package/dist/steps/execute/manifest.yaml +0 -28
- package/dist/steps/plan/01-load-context.md +0 -22
- package/dist/steps/plan/02-anchor-confirm.md +0 -1
- package/dist/steps/plan/03-expand-tasks.md +0 -33
- package/dist/steps/plan/04-mark-order.md +0 -15
- package/dist/steps/plan/05-e2e-planning.md +0 -17
- package/dist/steps/plan/06-self-check.md +0 -16
- package/dist/steps/plan/07-save.md +0 -1
- package/dist/steps/plan/manifest.yaml +0 -18
- package/dist/steps/scan/01-env-detect.md +0 -51
- package/dist/steps/scan/02-tech-stack.md +0 -16
- package/dist/steps/scan/03-conventions.md +0 -16
- package/dist/steps/scan/04-structure.md +0 -19
- package/dist/steps/scan/05-quality.md +0 -18
- package/dist/steps/scan/06-complete.md +0 -49
- package/dist/steps/scan/manifest.yaml +0 -16
- package/dist/steps/verify/01-load-specs.md +0 -28
- package/dist/steps/verify/02-check-tasks.md +0 -1
- package/dist/steps/verify/03-check-design.md +0 -6
- package/dist/steps/verify/04-run-tests.md +0 -7
- package/dist/steps/verify/05-e2e-tests.md +0 -27
- package/dist/steps/verify/05b-e2e-fix.md +0 -33
- package/dist/steps/verify/06-code-quality.md +0 -25
- package/dist/steps/verify/07-lint-check.md +0 -27
- package/dist/steps/verify/08-output-report.md +0 -14
- package/dist/steps/verify/manifest.yaml +0 -22
- package/packages/dashboard/dist/assets/index-DGe8CqeP.css +0 -1
- package/src/derive.js +0 -147
- 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
|
+
}
|