sillyspec 3.20.2 → 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 -21
- package/.claude/skills/sillyspec-auto/SKILL.md +83 -83
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -44
- package/.claude/skills/sillyspec-commit/SKILL.md +106 -106
- package/.claude/skills/sillyspec-continue/SKILL.md +45 -45
- package/.claude/skills/sillyspec-doctor/SKILL.md +31 -31
- package/.claude/skills/sillyspec-execute/SKILL.md +30 -30
- package/.claude/skills/sillyspec-explore/SKILL.md +109 -109
- package/.claude/skills/sillyspec-knowledge/SKILL.md +269 -269
- package/.claude/skills/sillyspec-plan/SKILL.md +21 -21
- package/.claude/skills/sillyspec-propose/SKILL.md +21 -21
- package/.claude/skills/sillyspec-quick/SKILL.md +21 -21
- package/.claude/skills/sillyspec-resume/SKILL.md +68 -68
- package/.claude/skills/sillyspec-scan/SKILL.md +21 -21
- package/.claude/skills/sillyspec-state/SKILL.md +54 -54
- package/.claude/skills/sillyspec-status/SKILL.md +21 -21
- package/.claude/skills/sillyspec-verify/SKILL.md +21 -21
- package/.claude/skills/sillyspec-workspace/SKILL.md +157 -157
- package/.husky/pre-push +13 -13
- package/CLAUDE.md +18 -18
- package/README.md +198 -188
- package/SKILL.md +90 -91
- package/bin/sillyspec.js +2 -2
- package/docs/brainstorm-plan-contract.md +64 -64
- package/docs/plan-execute-contract.md +123 -123
- package/docs/platform-scan-protocol.md +298 -298
- package/docs/revision-mode.md +115 -115
- package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -99
- package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -218
- package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -167
- package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -148
- package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -193
- package/docs/sillyspec/file-lifecycle.md +125 -125
- package/docs/workflow-contract-regression.md +106 -106
- package/docs/worktree-isolation.md +252 -252
- package/package.json +40 -40
- package/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -7446
- package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -1
- package/packages/dashboard/dist/index.html +16 -16
- 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/server/executor.js +86 -86
- package/packages/dashboard/server/index.js +588 -588
- package/packages/dashboard/server/parser.js +526 -526
- package/packages/dashboard/server/watcher.js +344 -344
- package/packages/dashboard/src/App.vue +558 -558
- package/packages/dashboard/src/components/ActionBar.vue +93 -93
- package/packages/dashboard/src/components/CommandPalette.vue +96 -96
- package/packages/dashboard/src/components/DetailPanel.vue +137 -137
- package/packages/dashboard/src/components/LogStream.vue +65 -65
- package/packages/dashboard/src/components/PipelineStage.vue +95 -95
- package/packages/dashboard/src/components/PipelineView.vue +156 -156
- package/packages/dashboard/src/components/ProjectList.vue +210 -210
- package/packages/dashboard/src/components/StageBadge.vue +67 -67
- package/packages/dashboard/src/components/StepCard.vue +94 -94
- 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 +170 -170
- package/packages/dashboard/src/composables/useKeyboard.js +119 -119
- 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 -158
- package/src/change-list.js +52 -52
- package/src/change-risk-profile.js +352 -352
- package/src/classify-change.js +73 -73
- package/src/constants.js +70 -70
- package/src/contract-matrix.js +278 -278
- package/src/db.js +201 -201
- package/src/endpoint-extractor.js +315 -315
- package/src/hooks/claude-pre-tool-use.cjs +125 -125
- package/src/hooks/worktree-guard.js +653 -653
- package/src/index.js +922 -900
- package/src/init.js +431 -431
- package/src/knowledge-match.js +130 -130
- package/src/migrate.js +117 -117
- package/src/modules.js +482 -482
- package/src/progress.js +1734 -1734
- package/src/run.js +3465 -3358
- package/src/scan-postcheck.js +387 -383
- package/src/setup.js +398 -398
- package/src/stage-contract.js +700 -700
- package/src/stages/archive.js +160 -160
- package/src/stages/brainstorm-auto.js +229 -229
- package/src/stages/brainstorm.js +645 -645
- package/src/stages/doctor.js +365 -365
- package/src/stages/execute.js +625 -625
- package/src/stages/explore.js +34 -34
- package/src/stages/index.js +29 -29
- package/src/stages/knowledge.js +498 -498
- package/src/stages/plan-postcheck.js +511 -513
- package/src/stages/plan.js +582 -582
- package/src/stages/propose.js +174 -174
- package/src/stages/quick.js +82 -82
- package/src/stages/scan.js +558 -558
- package/src/stages/status.js +65 -65
- package/src/stages/verify.js +322 -322
- package/src/sync.js +497 -497
- package/src/task-review.js +346 -346
- package/src/workflow.js +785 -785
- package/src/worktree-apply.js +549 -549
- package/src/worktree-deps.js +185 -0
- package/src/worktree.js +982 -932
- package/templates/workflows/archive-impact.yaml +79 -79
- package/templates/workflows/scan-docs.yaml +132 -132
- package/test/brainstorm-plan-contract.test.mjs +273 -273
- package/test/check-syntax.mjs +26 -26
- package/test/contract-artifacts.test.mjs +323 -323
- package/test/decision-supersede.test.mjs +277 -277
- package/test/knowledge-match.test.mjs +231 -231
- package/test/plan-execute-contract.test.mjs +330 -330
- package/test/plan-optimization.test.mjs +572 -572
- package/test/platform-artifacts.test.mjs +166 -166
- package/test/platform-failure-samples.test.mjs +199 -199
- package/test/platform-recovery-chain.test.mjs +167 -167
- package/test/platform-recovery.test.mjs +136 -136
- package/test/platform-scan-p0.test.mjs +168 -168
- package/test/revision-v1.test.mjs +1145 -1145
- package/test/run-scan-project-parse.test.mjs +200 -200
- package/test/run-tests.mjs +48 -48
- package/test/scan-knowledge.test.mjs +175 -175
- package/test/scan-paths.test.mjs +68 -68
- package/test/scan-postcheck.test.mjs +197 -197
- package/test/spec-dir.test.mjs +206 -206
- package/test/stage-contract.test.mjs +299 -299
- package/test/stage-definitions.test.mjs +39 -39
- package/test/wait-gates.test.mjs +496 -496
- package/test/worktree-deps-provision.test.mjs +148 -0
- package/test/worktree-guard.test.mjs +71 -71
- package/test/worktree-native-overlay.test.mjs +188 -188
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
import { reactive, computed } from 'vue'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Dashboard state management composable
|
|
5
|
-
* @returns {object} Dashboard state and methods
|
|
6
|
-
*/
|
|
7
|
-
export function useDashboard() {
|
|
8
|
-
// Reactive state
|
|
9
|
-
const state = reactive({
|
|
10
|
-
projects: [],
|
|
11
|
-
activeProject: null,
|
|
12
|
-
activeStep: null,
|
|
13
|
-
logs: [],
|
|
14
|
-
isPanelOpen: true,
|
|
15
|
-
executingProject: null,
|
|
16
|
-
isLoading: true,
|
|
17
|
-
activeTab: 'pipeline',
|
|
18
|
-
docs: { groups: [] },
|
|
19
|
-
selectedDocFile: null,
|
|
20
|
-
docContent: '',
|
|
21
|
-
docLoading: false
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Get project by name
|
|
26
|
-
* @param {string} name - Project name
|
|
27
|
-
* @returns {object|null} Project or null
|
|
28
|
-
*/
|
|
29
|
-
function getProject(path) {
|
|
30
|
-
return state.projects.find(p => p.path === path) || null
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Select a project as active
|
|
35
|
-
* @param {object|string} project - Project object or path
|
|
36
|
-
*/
|
|
37
|
-
function selectProject(project) {
|
|
38
|
-
const proj = typeof project === 'string'
|
|
39
|
-
? getProject(project)
|
|
40
|
-
: project
|
|
41
|
-
|
|
42
|
-
if (proj) {
|
|
43
|
-
state.activeProject = proj
|
|
44
|
-
state.activeStep = null
|
|
45
|
-
state.logs = []
|
|
46
|
-
state.selectedDocFile = null
|
|
47
|
-
state.docContent = ''
|
|
48
|
-
|
|
49
|
-
// Load initial logs from project state if available
|
|
50
|
-
if (proj.state?.progress?.currentLogs) {
|
|
51
|
-
appendLog(proj.state.progress.currentLogs)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Select a step within active project
|
|
58
|
-
* @param {object} step - Step object
|
|
59
|
-
*/
|
|
60
|
-
function selectStep(step) {
|
|
61
|
-
state.activeStep = step
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Append log lines to logs buffer
|
|
66
|
-
* @param {string|string[]} lines - Log line(s) to append
|
|
67
|
-
*/
|
|
68
|
-
function appendLog(lines) {
|
|
69
|
-
const newLines = Array.isArray(lines) ? lines : [lines]
|
|
70
|
-
|
|
71
|
-
for (const line of newLines) {
|
|
72
|
-
const timestamp = new Date().toISOString()
|
|
73
|
-
state.logs.push({
|
|
74
|
-
id: `${timestamp}-${Math.random().toString(36).substr(2, 9)}`,
|
|
75
|
-
timestamp,
|
|
76
|
-
content: line,
|
|
77
|
-
type: 'info'
|
|
78
|
-
})
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Keep only last 500 lines
|
|
82
|
-
if (state.logs.length > 500) {
|
|
83
|
-
state.logs = state.logs.slice(-500)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Clear logs buffer
|
|
89
|
-
*/
|
|
90
|
-
function clearLogs() {
|
|
91
|
-
state.logs = []
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Toggle detail panel open/closed
|
|
96
|
-
*/
|
|
97
|
-
function togglePanel() {
|
|
98
|
-
state.isPanelOpen = !state.isPanelOpen
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Open detail panel
|
|
103
|
-
*/
|
|
104
|
-
function openPanel() {
|
|
105
|
-
state.isPanelOpen = true
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Close detail panel
|
|
110
|
-
*/
|
|
111
|
-
function closePanel() {
|
|
112
|
-
state.isPanelOpen = false
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Update projects list
|
|
117
|
-
* @param {array} projects - New projects array
|
|
118
|
-
*/
|
|
1
|
+
import { reactive, computed } from 'vue'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dashboard state management composable
|
|
5
|
+
* @returns {object} Dashboard state and methods
|
|
6
|
+
*/
|
|
7
|
+
export function useDashboard() {
|
|
8
|
+
// Reactive state
|
|
9
|
+
const state = reactive({
|
|
10
|
+
projects: [],
|
|
11
|
+
activeProject: null,
|
|
12
|
+
activeStep: null,
|
|
13
|
+
logs: [],
|
|
14
|
+
isPanelOpen: true,
|
|
15
|
+
executingProject: null,
|
|
16
|
+
isLoading: true,
|
|
17
|
+
activeTab: 'pipeline',
|
|
18
|
+
docs: { groups: [] },
|
|
19
|
+
selectedDocFile: null,
|
|
20
|
+
docContent: '',
|
|
21
|
+
docLoading: false
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get project by name
|
|
26
|
+
* @param {string} name - Project name
|
|
27
|
+
* @returns {object|null} Project or null
|
|
28
|
+
*/
|
|
29
|
+
function getProject(path) {
|
|
30
|
+
return state.projects.find(p => p.path === path) || null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Select a project as active
|
|
35
|
+
* @param {object|string} project - Project object or path
|
|
36
|
+
*/
|
|
37
|
+
function selectProject(project) {
|
|
38
|
+
const proj = typeof project === 'string'
|
|
39
|
+
? getProject(project)
|
|
40
|
+
: project
|
|
41
|
+
|
|
42
|
+
if (proj) {
|
|
43
|
+
state.activeProject = proj
|
|
44
|
+
state.activeStep = null
|
|
45
|
+
state.logs = []
|
|
46
|
+
state.selectedDocFile = null
|
|
47
|
+
state.docContent = ''
|
|
48
|
+
|
|
49
|
+
// Load initial logs from project state if available
|
|
50
|
+
if (proj.state?.progress?.currentLogs) {
|
|
51
|
+
appendLog(proj.state.progress.currentLogs)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Select a step within active project
|
|
58
|
+
* @param {object} step - Step object
|
|
59
|
+
*/
|
|
60
|
+
function selectStep(step) {
|
|
61
|
+
state.activeStep = step
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Append log lines to logs buffer
|
|
66
|
+
* @param {string|string[]} lines - Log line(s) to append
|
|
67
|
+
*/
|
|
68
|
+
function appendLog(lines) {
|
|
69
|
+
const newLines = Array.isArray(lines) ? lines : [lines]
|
|
70
|
+
|
|
71
|
+
for (const line of newLines) {
|
|
72
|
+
const timestamp = new Date().toISOString()
|
|
73
|
+
state.logs.push({
|
|
74
|
+
id: `${timestamp}-${Math.random().toString(36).substr(2, 9)}`,
|
|
75
|
+
timestamp,
|
|
76
|
+
content: line,
|
|
77
|
+
type: 'info'
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Keep only last 500 lines
|
|
82
|
+
if (state.logs.length > 500) {
|
|
83
|
+
state.logs = state.logs.slice(-500)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Clear logs buffer
|
|
89
|
+
*/
|
|
90
|
+
function clearLogs() {
|
|
91
|
+
state.logs = []
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Toggle detail panel open/closed
|
|
96
|
+
*/
|
|
97
|
+
function togglePanel() {
|
|
98
|
+
state.isPanelOpen = !state.isPanelOpen
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Open detail panel
|
|
103
|
+
*/
|
|
104
|
+
function openPanel() {
|
|
105
|
+
state.isPanelOpen = true
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Close detail panel
|
|
110
|
+
*/
|
|
111
|
+
function closePanel() {
|
|
112
|
+
state.isPanelOpen = false
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Update projects list
|
|
117
|
+
* @param {array} projects - New projects array
|
|
118
|
+
*/
|
|
119
119
|
function updateProjects(projects) {
|
|
120
120
|
state.isLoading = false
|
|
121
121
|
state.projects = projects
|
|
@@ -155,59 +155,59 @@ export function useDashboard() {
|
|
|
155
155
|
state.activeProject = updated
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Set executing project state
|
|
161
|
-
* @param {string|null} projectName - Project name or null
|
|
162
|
-
*/
|
|
163
|
-
function setExecuting(projectName) {
|
|
164
|
-
state.executingProject = projectName
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Check if a project is currently executing
|
|
169
|
-
* @param {string} projectName - Project name
|
|
170
|
-
* @returns {boolean}
|
|
171
|
-
*/
|
|
172
|
-
function isExecuting(projectName) {
|
|
173
|
-
return state.executingProject === projectName
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Computed properties
|
|
177
|
-
const activeProjectName = computed(() => state.activeProject?.name || null)
|
|
178
|
-
const activeProjectPath = computed(() => state.activeProject?.path || null)
|
|
179
|
-
const activeProjectStage = computed(() => state.activeProject?.state?.currentStage || null)
|
|
180
|
-
const hasProjects = computed(() => state.projects.length > 0)
|
|
181
|
-
const activeProjectSteps = computed(() => {
|
|
182
|
-
if (!state.activeProject?.state?.progress?.stages) return []
|
|
183
|
-
const currentStage = state.activeProject.state.currentStage
|
|
184
|
-
const stageData = state.activeProject.state.progress.stages[currentStage]
|
|
185
|
-
return stageData?.steps || []
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
return {
|
|
189
|
-
state,
|
|
190
|
-
getProject,
|
|
191
|
-
selectProject,
|
|
192
|
-
selectStep,
|
|
193
|
-
appendLog,
|
|
194
|
-
clearLogs,
|
|
195
|
-
togglePanel,
|
|
196
|
-
openPanel,
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Set executing project state
|
|
161
|
+
* @param {string|null} projectName - Project name or null
|
|
162
|
+
*/
|
|
163
|
+
function setExecuting(projectName) {
|
|
164
|
+
state.executingProject = projectName
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Check if a project is currently executing
|
|
169
|
+
* @param {string} projectName - Project name
|
|
170
|
+
* @returns {boolean}
|
|
171
|
+
*/
|
|
172
|
+
function isExecuting(projectName) {
|
|
173
|
+
return state.executingProject === projectName
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Computed properties
|
|
177
|
+
const activeProjectName = computed(() => state.activeProject?.name || null)
|
|
178
|
+
const activeProjectPath = computed(() => state.activeProject?.path || null)
|
|
179
|
+
const activeProjectStage = computed(() => state.activeProject?.state?.currentStage || null)
|
|
180
|
+
const hasProjects = computed(() => state.projects.length > 0)
|
|
181
|
+
const activeProjectSteps = computed(() => {
|
|
182
|
+
if (!state.activeProject?.state?.progress?.stages) return []
|
|
183
|
+
const currentStage = state.activeProject.state.currentStage
|
|
184
|
+
const stageData = state.activeProject.state.progress.stages[currentStage]
|
|
185
|
+
return stageData?.steps || []
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
state,
|
|
190
|
+
getProject,
|
|
191
|
+
selectProject,
|
|
192
|
+
selectStep,
|
|
193
|
+
appendLog,
|
|
194
|
+
clearLogs,
|
|
195
|
+
togglePanel,
|
|
196
|
+
openPanel,
|
|
197
197
|
closePanel,
|
|
198
198
|
updateProjects,
|
|
199
199
|
updateProject,
|
|
200
200
|
setExecuting,
|
|
201
|
-
isExecuting,
|
|
202
|
-
activeProjectName,
|
|
203
|
-
activeProjectPath,
|
|
204
|
-
activeProjectStage,
|
|
205
|
-
hasProjects,
|
|
206
|
-
activeProjectSteps,
|
|
207
|
-
setActiveTab(tab) { state.activeTab = tab },
|
|
208
|
-
updateDocs(docs) { state.docs = docs },
|
|
209
|
-
selectDocFile(file) { state.selectedDocFile = file },
|
|
210
|
-
setDocContent(content) { state.docContent = content },
|
|
211
|
-
setDocLoading(loading) { state.docLoading = loading }
|
|
212
|
-
}
|
|
213
|
-
}
|
|
201
|
+
isExecuting,
|
|
202
|
+
activeProjectName,
|
|
203
|
+
activeProjectPath,
|
|
204
|
+
activeProjectStage,
|
|
205
|
+
hasProjects,
|
|
206
|
+
activeProjectSteps,
|
|
207
|
+
setActiveTab(tab) { state.activeTab = tab },
|
|
208
|
+
updateDocs(docs) { state.docs = docs },
|
|
209
|
+
selectDocFile(file) { state.selectedDocFile = file },
|
|
210
|
+
setDocContent(content) { state.docContent = content },
|
|
211
|
+
setDocLoading(loading) { state.docLoading = loading }
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -1,119 +1,119 @@
|
|
|
1
|
-
import { onMounted, onUnmounted } from 'vue'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Keyboard shortcuts composable
|
|
5
|
-
* @param {object} options - Configuration options
|
|
6
|
-
* @returns {object} Keyboard API
|
|
7
|
-
*/
|
|
8
|
-
export function useKeyboard(options = {}) {
|
|
9
|
-
const {
|
|
10
|
-
onCmdK = null,
|
|
11
|
-
onJ = null,
|
|
12
|
-
onK = null,
|
|
13
|
-
onEscape = null,
|
|
14
|
-
onEnter = null,
|
|
15
|
-
onArrowUp = null,
|
|
16
|
-
onArrowDown = null,
|
|
17
|
-
disabled: initialDisabled = false
|
|
18
|
-
} = options
|
|
19
|
-
|
|
20
|
-
let isDisabled = initialDisabled
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Handle keyboard events
|
|
24
|
-
* @param {KeyboardEvent} event
|
|
25
|
-
*/
|
|
26
|
-
function handleKeyDown(event) {
|
|
27
|
-
if (isDisabled) return
|
|
28
|
-
|
|
29
|
-
// Ignore if in input field
|
|
30
|
-
const target = event.target
|
|
31
|
-
if (target.tagName === 'INPUT' ||
|
|
32
|
-
target.tagName === 'TEXTAREA' ||
|
|
33
|
-
target.contentEditable === 'true') {
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0
|
|
38
|
-
const cmdOrCtrl = isMac ? event.metaKey : event.ctrlKey
|
|
39
|
-
|
|
40
|
-
// Cmd/Ctrl + K
|
|
41
|
-
if (cmdOrCtrl && event.key === 'k') {
|
|
42
|
-
event.preventDefault()
|
|
43
|
-
if (onCmdK) onCmdK()
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// J - navigate down
|
|
47
|
-
if (event.key === 'j' && !cmdOrCtrl) {
|
|
48
|
-
event.preventDefault()
|
|
49
|
-
if (onJ) onJ()
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// K - navigate up
|
|
53
|
-
if (event.key === 'k' && !cmdOrCtrl) {
|
|
54
|
-
event.preventDefault()
|
|
55
|
-
if (onK) onK()
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Escape
|
|
59
|
-
if (event.key === 'Escape') {
|
|
60
|
-
event.preventDefault()
|
|
61
|
-
if (onEscape) onEscape()
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Enter
|
|
65
|
-
if (event.key === 'Enter' && !cmdOrCtrl) {
|
|
66
|
-
event.preventDefault()
|
|
67
|
-
if (onEnter) onEnter()
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Arrow Up
|
|
71
|
-
if (event.key === 'ArrowUp' && !cmdOrCtrl) {
|
|
72
|
-
event.preventDefault()
|
|
73
|
-
if (onArrowUp) onArrowUp()
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Arrow Down
|
|
77
|
-
if (event.key === 'ArrowDown' && !cmdOrCtrl) {
|
|
78
|
-
event.preventDefault()
|
|
79
|
-
if (onArrowDown) onArrowDown()
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
onMounted(() => {
|
|
84
|
-
window.addEventListener('keydown', handleKeyDown)
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
onUnmounted(() => {
|
|
88
|
-
window.removeEventListener('keydown', handleKeyDown)
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
disable: () => { isDisabled = true },
|
|
93
|
-
enable: () => { isDisabled = false }
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Global keyboard shortcuts for dashboard
|
|
99
|
-
* @param {object} callbacks - Callback functions for each shortcut
|
|
100
|
-
* @returns {object} Keyboard API
|
|
101
|
-
*/
|
|
102
|
-
export function useDashboardKeyboard(callbacks = {}) {
|
|
103
|
-
const {
|
|
104
|
-
onOpenCommandPalette,
|
|
105
|
-
onNavigateDown,
|
|
106
|
-
onNavigateUp,
|
|
107
|
-
onClose,
|
|
108
|
-
onSelect,
|
|
109
|
-
onPanelToggle
|
|
110
|
-
} = callbacks
|
|
111
|
-
|
|
112
|
-
return useKeyboard({
|
|
113
|
-
onCmdK: onOpenCommandPalette,
|
|
114
|
-
onJ: onNavigateDown,
|
|
115
|
-
onK: onNavigateUp,
|
|
116
|
-
onEscape: onClose,
|
|
117
|
-
onEnter: onSelect
|
|
118
|
-
})
|
|
119
|
-
}
|
|
1
|
+
import { onMounted, onUnmounted } from 'vue'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Keyboard shortcuts composable
|
|
5
|
+
* @param {object} options - Configuration options
|
|
6
|
+
* @returns {object} Keyboard API
|
|
7
|
+
*/
|
|
8
|
+
export function useKeyboard(options = {}) {
|
|
9
|
+
const {
|
|
10
|
+
onCmdK = null,
|
|
11
|
+
onJ = null,
|
|
12
|
+
onK = null,
|
|
13
|
+
onEscape = null,
|
|
14
|
+
onEnter = null,
|
|
15
|
+
onArrowUp = null,
|
|
16
|
+
onArrowDown = null,
|
|
17
|
+
disabled: initialDisabled = false
|
|
18
|
+
} = options
|
|
19
|
+
|
|
20
|
+
let isDisabled = initialDisabled
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Handle keyboard events
|
|
24
|
+
* @param {KeyboardEvent} event
|
|
25
|
+
*/
|
|
26
|
+
function handleKeyDown(event) {
|
|
27
|
+
if (isDisabled) return
|
|
28
|
+
|
|
29
|
+
// Ignore if in input field
|
|
30
|
+
const target = event.target
|
|
31
|
+
if (target.tagName === 'INPUT' ||
|
|
32
|
+
target.tagName === 'TEXTAREA' ||
|
|
33
|
+
target.contentEditable === 'true') {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0
|
|
38
|
+
const cmdOrCtrl = isMac ? event.metaKey : event.ctrlKey
|
|
39
|
+
|
|
40
|
+
// Cmd/Ctrl + K
|
|
41
|
+
if (cmdOrCtrl && event.key === 'k') {
|
|
42
|
+
event.preventDefault()
|
|
43
|
+
if (onCmdK) onCmdK()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// J - navigate down
|
|
47
|
+
if (event.key === 'j' && !cmdOrCtrl) {
|
|
48
|
+
event.preventDefault()
|
|
49
|
+
if (onJ) onJ()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// K - navigate up
|
|
53
|
+
if (event.key === 'k' && !cmdOrCtrl) {
|
|
54
|
+
event.preventDefault()
|
|
55
|
+
if (onK) onK()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Escape
|
|
59
|
+
if (event.key === 'Escape') {
|
|
60
|
+
event.preventDefault()
|
|
61
|
+
if (onEscape) onEscape()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Enter
|
|
65
|
+
if (event.key === 'Enter' && !cmdOrCtrl) {
|
|
66
|
+
event.preventDefault()
|
|
67
|
+
if (onEnter) onEnter()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Arrow Up
|
|
71
|
+
if (event.key === 'ArrowUp' && !cmdOrCtrl) {
|
|
72
|
+
event.preventDefault()
|
|
73
|
+
if (onArrowUp) onArrowUp()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Arrow Down
|
|
77
|
+
if (event.key === 'ArrowDown' && !cmdOrCtrl) {
|
|
78
|
+
event.preventDefault()
|
|
79
|
+
if (onArrowDown) onArrowDown()
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
onMounted(() => {
|
|
84
|
+
window.addEventListener('keydown', handleKeyDown)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
onUnmounted(() => {
|
|
88
|
+
window.removeEventListener('keydown', handleKeyDown)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
disable: () => { isDisabled = true },
|
|
93
|
+
enable: () => { isDisabled = false }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Global keyboard shortcuts for dashboard
|
|
99
|
+
* @param {object} callbacks - Callback functions for each shortcut
|
|
100
|
+
* @returns {object} Keyboard API
|
|
101
|
+
*/
|
|
102
|
+
export function useDashboardKeyboard(callbacks = {}) {
|
|
103
|
+
const {
|
|
104
|
+
onOpenCommandPalette,
|
|
105
|
+
onNavigateDown,
|
|
106
|
+
onNavigateUp,
|
|
107
|
+
onClose,
|
|
108
|
+
onSelect,
|
|
109
|
+
onPanelToggle
|
|
110
|
+
} = callbacks
|
|
111
|
+
|
|
112
|
+
return useKeyboard({
|
|
113
|
+
onCmdK: onOpenCommandPalette,
|
|
114
|
+
onJ: onNavigateDown,
|
|
115
|
+
onK: onNavigateUp,
|
|
116
|
+
onEscape: onClose,
|
|
117
|
+
onEnter: onSelect
|
|
118
|
+
})
|
|
119
|
+
}
|