trackops 2.0.3 → 2.0.5
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/LICENSE +21 -21
- package/README.md +695 -402
- package/bin/trackops.js +116 -116
- package/lib/config.js +326 -326
- package/lib/control.js +208 -208
- package/lib/env.js +244 -244
- package/lib/init.js +325 -325
- package/lib/locale.js +24 -0
- package/lib/opera-bootstrap.js +941 -874
- package/lib/opera.js +494 -477
- package/lib/preferences.js +74 -74
- package/lib/registry.js +214 -196
- package/lib/release.js +56 -56
- package/lib/runtime-state.js +144 -144
- package/lib/server.js +312 -207
- package/lib/skills.js +74 -57
- package/lib/workspace.js +260 -260
- package/locales/en.json +192 -166
- package/locales/es.json +192 -166
- package/package.json +61 -58
- package/scripts/postinstall-locale.js +21 -21
- package/scripts/skills-marketplace-smoke.js +124 -124
- package/scripts/smoke-tests.js +558 -554
- package/scripts/sync-skill-version.js +21 -21
- package/scripts/validate-skill.js +103 -103
- package/skills/trackops/SKILL.md +126 -122
- package/skills/trackops/agents/openai.yaml +7 -7
- package/skills/trackops/locales/en/SKILL.md +126 -122
- package/skills/trackops/locales/en/references/activation.md +94 -75
- package/skills/trackops/locales/en/references/troubleshooting.md +73 -55
- package/skills/trackops/locales/en/references/workflow.md +55 -32
- package/skills/trackops/references/activation.md +94 -75
- package/skills/trackops/references/troubleshooting.md +73 -55
- package/skills/trackops/references/workflow.md +55 -32
- package/skills/trackops/skill.json +29 -29
- package/templates/hooks/post-checkout +2 -2
- package/templates/hooks/post-commit +2 -2
- package/templates/hooks/post-merge +2 -2
- package/templates/opera/agent.md +28 -27
- package/templates/opera/architecture/dependency-graph.md +24 -24
- package/templates/opera/architecture/runtime-automation.md +24 -24
- package/templates/opera/architecture/runtime-operations.md +34 -34
- package/templates/opera/en/agent.md +22 -21
- package/templates/opera/en/architecture/dependency-graph.md +24 -24
- package/templates/opera/en/architecture/runtime-automation.md +24 -24
- package/templates/opera/en/architecture/runtime-operations.md +34 -34
- package/templates/opera/en/reviews/delivery-audit.md +18 -18
- package/templates/opera/en/reviews/integration-audit.md +18 -18
- package/templates/opera/en/router.md +24 -19
- package/templates/opera/references/autonomy-and-recovery.md +117 -117
- package/templates/opera/references/opera-cycle.md +193 -193
- package/templates/opera/registry.md +28 -28
- package/templates/opera/reviews/delivery-audit.md +18 -18
- package/templates/opera/reviews/integration-audit.md +18 -18
- package/templates/opera/router.md +54 -49
- package/templates/skills/changelog-updater/SKILL.md +69 -69
- package/templates/skills/commiter/SKILL.md +99 -99
- package/templates/skills/opera-contract-auditor/SKILL.md +38 -38
- package/templates/skills/opera-contract-auditor/locales/en/SKILL.md +38 -38
- package/templates/skills/opera-policy-guard/SKILL.md +26 -26
- package/templates/skills/opera-policy-guard/locales/en/SKILL.md +26 -26
- package/templates/skills/opera-skill/SKILL.md +279 -0
- package/templates/skills/opera-skill/locales/en/SKILL.md +279 -0
- package/templates/skills/opera-skill/locales/en/references/phase-dod.md +138 -0
- package/templates/skills/opera-skill/references/phase-dod.md +138 -0
- package/templates/skills/project-starter-skill/SKILL.md +150 -131
- package/templates/skills/project-starter-skill/locales/en/SKILL.md +143 -105
- package/templates/skills/project-starter-skill/references/opera-cycle.md +195 -193
- package/ui/css/base.css +284 -266
- package/ui/css/charts.css +425 -327
- package/ui/css/components.css +1107 -570
- package/ui/css/onboarding.css +133 -0
- package/ui/css/panels.css +345 -406
- package/ui/css/terminal.css +125 -0
- package/ui/css/timeline.css +58 -0
- package/ui/css/tokens.css +284 -227
- package/ui/favicon.svg +5 -5
- package/ui/index.html +99 -96
- package/ui/js/api.js +49 -13
- package/ui/js/app.js +28 -32
- package/ui/js/charts.js +526 -0
- package/ui/js/console-logger.js +172 -172
- package/ui/js/filters.js +247 -0
- package/ui/js/icons.js +129 -104
- package/ui/js/keyboard.js +229 -0
- package/ui/js/onboarding.js +33 -42
- package/ui/js/router.js +142 -125
- package/ui/js/theme.js +100 -100
- package/ui/js/time-tracker.js +248 -248
- package/ui/js/views/board.js +84 -114
- package/ui/js/views/dashboard.js +870 -0
- package/ui/js/views/flash.js +47 -47
- package/ui/js/views/projects.js +745 -0
- package/ui/js/views/scrum.js +476 -0
- package/ui/js/views/settings.js +153 -203
- package/ui/js/views/sidebar.js +37 -31
- package/ui/js/views/tasks.js +218 -101
- package/ui/js/views/timeline.js +265 -0
- package/ui/js/views/topbar.js +94 -107
- package/ui/app.js +0 -950
- package/ui/js/views/insights.js +0 -340
- package/ui/js/views/overview.js +0 -369
- package/ui/styles.css +0 -688
package/ui/js/views/settings.js
CHANGED
|
@@ -8,10 +8,11 @@ import * as api from '../api.js';
|
|
|
8
8
|
import { flash } from './flash.js';
|
|
9
9
|
import { esc, formatDate } from '../utils.js';
|
|
10
10
|
import { t } from '../i18n.js';
|
|
11
|
+
import * as skillsView from './skills.js';
|
|
11
12
|
|
|
12
|
-
function applyLocaleState(payload) {
|
|
13
|
-
if (!payload) return;
|
|
14
|
-
state.update('payload', payload);
|
|
13
|
+
function applyLocaleState(payload) {
|
|
14
|
+
if (!payload) return;
|
|
15
|
+
state.update('payload', payload);
|
|
15
16
|
if (payload.i18n) {
|
|
16
17
|
state.update({
|
|
17
18
|
phases: payload.i18n.phases || [],
|
|
@@ -19,100 +20,98 @@ function applyLocaleState(payload) {
|
|
|
19
20
|
locale: payload.i18n.locale || 'es',
|
|
20
21
|
messages: payload.i18n.messages || {},
|
|
21
22
|
});
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function renderWorkspacePanel(payload) {
|
|
26
|
-
const project = payload?.project;
|
|
27
|
-
if (!project) return '';
|
|
28
|
-
|
|
29
|
-
return `
|
|
30
|
-
<div class="panel">
|
|
31
|
-
<div class="panel-header">
|
|
32
|
-
<p class="panel-title">${t('ui.settings.workspaceTitle', {}, 'Workspace')}</p>
|
|
33
|
-
<span class="badge badge-muted">${esc(project.layout || 'legacy')}</span>
|
|
34
|
-
</div>
|
|
35
|
-
<div class="panel-body" style="display:flex;flex-direction:column;gap:var(--space-3)">
|
|
36
|
-
<div class="info-row">
|
|
37
|
-
<p class="label-sm">${t('ui.settings.workspaceRoot', {}, 'Workspace root')}</p>
|
|
38
|
-
<p class="value">${esc(project.workspaceRoot || project.root || '—')}</p>
|
|
39
|
-
</div>
|
|
40
|
-
<div class="info-row">
|
|
41
|
-
<p class="label-sm">${t('ui.settings.appRoot', {}, 'App root')}</p>
|
|
42
|
-
<p class="value">${esc(project.appRoot || project.root || '—')}</p>
|
|
43
|
-
</div>
|
|
44
|
-
<div class="info-row">
|
|
45
|
-
<p class="label-sm">${t('ui.settings.opsRoot', {}, 'Ops root')}</p>
|
|
46
|
-
<p class="value">${esc(project.opsRoot || project.root || '—')}</p>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
`;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function renderEnvPanel(envState) {
|
|
54
|
-
if (!envState) return '';
|
|
55
|
-
|
|
56
|
-
const required = Array.isArray(envState.requiredKeys) ? envState.requiredKeys : [];
|
|
57
|
-
const present = Array.isArray(envState.presentKeys) ? envState.presentKeys : [];
|
|
58
|
-
const missing = Array.isArray(envState.missingKeys) ? envState.missingKeys : [];
|
|
59
|
-
const files = envState.files || {};
|
|
60
|
-
|
|
61
|
-
return `
|
|
62
|
-
<div class="panel">
|
|
63
|
-
<div class="panel-header">
|
|
64
|
-
<p class="panel-title">${t('ui.settings.envTitle', {}, 'Environment')}</p>
|
|
65
|
-
<span class="badge badge-${missing.length ? 'warning' : 'success'}">${missing.length ? t('ui.settings.envMissingBadge', { count: missing.length }, `${missing.length} missing`) : t('ui.settings.envHealthyBadge', {}, 'Ready')}</span>
|
|
66
|
-
</div>
|
|
67
|
-
<div class="panel-body" style="display:flex;flex-direction:column;gap:var(--space-3)">
|
|
68
|
-
<div class="info-row">
|
|
69
|
-
<p class="label-sm">${t('ui.settings.envBridgeMode', {}, 'Bridge mode')}</p>
|
|
70
|
-
<p class="value">${esc(envState.bridgeMode || 'none')}</p>
|
|
71
|
-
</div>
|
|
72
|
-
<div class="info-row">
|
|
73
|
-
<p class="label-sm">${t('ui.settings.envRootFile', {}, 'Root .env')}</p>
|
|
74
|
-
<p class="value">${esc(files.rootEnv || '—')}</p>
|
|
75
|
-
</div>
|
|
76
|
-
<div class="info-row">
|
|
77
|
-
<p class="label-sm">${t('ui.settings.envExampleFile', {}, '.env.example')}</p>
|
|
78
|
-
<p class="value">${esc(files.rootExample || '—')}</p>
|
|
79
|
-
</div>
|
|
80
|
-
<div class="info-row">
|
|
81
|
-
<p class="label-sm">${t('ui.settings.envAppBridge', {}, 'App bridge')}</p>
|
|
82
|
-
<p class="value">${esc(files.appBridge || '—')}</p>
|
|
83
|
-
</div>
|
|
84
|
-
<div class="info-row">
|
|
85
|
-
<p class="label-sm">${t('ui.settings.envRequiredKeys', {}, 'Required keys')}</p>
|
|
86
|
-
<p class="value">${required.length ? esc(required.join(', ')) : '—'}</p>
|
|
87
|
-
</div>
|
|
88
|
-
<div class="info-row">
|
|
89
|
-
<p class="label-sm">${t('ui.settings.envPresentKeys', {}, 'Present keys')}</p>
|
|
90
|
-
<p class="value">${present.length ? esc(present.join(', ')) : '—'}</p>
|
|
91
|
-
</div>
|
|
92
|
-
<div class="info-row">
|
|
93
|
-
<p class="label-sm">${t('ui.settings.envMissingKeys', {}, 'Missing keys')}</p>
|
|
94
|
-
<p class="value">${missing.length ? esc(missing.join(', ')) : t('ui.settings.envNoMissing', {}, 'None')}</p>
|
|
95
|
-
</div>
|
|
96
|
-
<div class="info-row">
|
|
97
|
-
<p class="label-sm">${t('ui.settings.envLastAudit', {}, 'Last audit')}</p>
|
|
98
|
-
<p class="value">${envState.lastAuditAt ? formatDate(envState.lastAuditAt, 'date') : '—'}</p>
|
|
99
|
-
</div>
|
|
100
|
-
<button class="btn btn-ghost btn-sm" type="button" id="sync-env-btn">
|
|
101
|
-
${icon('sync', 14)} ${t('ui.settings.envSync', {}, 'Sync env')}
|
|
102
|
-
</button>
|
|
103
|
-
</div>
|
|
104
|
-
</div>
|
|
105
|
-
`;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export async function render() {
|
|
109
|
-
const payload = state.getPayload();
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
const docsDirty = payload?.docsDirty || [];
|
|
115
|
-
const envState = payload?.env;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function renderWorkspacePanel(payload) {
|
|
27
|
+
const project = payload?.project;
|
|
28
|
+
if (!project) return '';
|
|
29
|
+
|
|
30
|
+
return `
|
|
31
|
+
<div class="glass-card panel">
|
|
32
|
+
<div class="panel-header">
|
|
33
|
+
<p class="panel-title">${t('ui.settings.workspaceTitle', {}, 'Workspace')}</p>
|
|
34
|
+
<span class="badge badge-muted">${esc(project.layout || 'legacy')}</span>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="panel-body" style="display:flex;flex-direction:column;gap:var(--space-3)">
|
|
37
|
+
<div class="info-row">
|
|
38
|
+
<p class="label-sm">${t('ui.settings.workspaceRoot', {}, 'Workspace root')}</p>
|
|
39
|
+
<p class="value">${esc(project.workspaceRoot || project.root || '—')}</p>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="info-row">
|
|
42
|
+
<p class="label-sm">${t('ui.settings.appRoot', {}, 'App root')}</p>
|
|
43
|
+
<p class="value">${esc(project.appRoot || project.root || '—')}</p>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="info-row">
|
|
46
|
+
<p class="label-sm">${t('ui.settings.opsRoot', {}, 'Ops root')}</p>
|
|
47
|
+
<p class="value">${esc(project.opsRoot || project.root || '—')}</p>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function renderEnvPanel(envState) {
|
|
55
|
+
if (!envState) return '';
|
|
56
|
+
|
|
57
|
+
const required = Array.isArray(envState.requiredKeys) ? envState.requiredKeys : [];
|
|
58
|
+
const present = Array.isArray(envState.presentKeys) ? envState.presentKeys : [];
|
|
59
|
+
const missing = Array.isArray(envState.missingKeys) ? envState.missingKeys : [];
|
|
60
|
+
const files = envState.files || {};
|
|
61
|
+
|
|
62
|
+
return `
|
|
63
|
+
<div class="glass-card panel">
|
|
64
|
+
<div class="panel-header">
|
|
65
|
+
<p class="panel-title">${t('ui.settings.envTitle', {}, 'Environment')}</p>
|
|
66
|
+
<span class="badge badge-${missing.length ? 'warning' : 'success'}">${missing.length ? t('ui.settings.envMissingBadge', { count: missing.length }, `${missing.length} missing`) : t('ui.settings.envHealthyBadge', {}, 'Ready')}</span>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="panel-body" style="display:flex;flex-direction:column;gap:var(--space-3)">
|
|
69
|
+
<div class="info-row">
|
|
70
|
+
<p class="label-sm">${t('ui.settings.envBridgeMode', {}, 'Bridge mode')}</p>
|
|
71
|
+
<p class="value">${esc(envState.bridgeMode || 'none')}</p>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="info-row">
|
|
74
|
+
<p class="label-sm">${t('ui.settings.envRootFile', {}, 'Root .env')}</p>
|
|
75
|
+
<p class="value">${esc(files.rootEnv || '—')}</p>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="info-row">
|
|
78
|
+
<p class="label-sm">${t('ui.settings.envExampleFile', {}, '.env.example')}</p>
|
|
79
|
+
<p class="value">${esc(files.rootExample || '—')}</p>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="info-row">
|
|
82
|
+
<p class="label-sm">${t('ui.settings.envAppBridge', {}, 'App bridge')}</p>
|
|
83
|
+
<p class="value">${esc(files.appBridge || '—')}</p>
|
|
84
|
+
</div>
|
|
85
|
+
<div class="info-row">
|
|
86
|
+
<p class="label-sm">${t('ui.settings.envRequiredKeys', {}, 'Required keys')}</p>
|
|
87
|
+
<p class="value">${required.length ? esc(required.join(', ')) : '—'}</p>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="info-row">
|
|
90
|
+
<p class="label-sm">${t('ui.settings.envPresentKeys', {}, 'Present keys')}</p>
|
|
91
|
+
<p class="value">${present.length ? esc(present.join(', ')) : '—'}</p>
|
|
92
|
+
</div>
|
|
93
|
+
<div class="info-row">
|
|
94
|
+
<p class="label-sm">${t('ui.settings.envMissingKeys', {}, 'Missing keys')}</p>
|
|
95
|
+
<p class="value">${missing.length ? esc(missing.join(', ')) : t('ui.settings.envNoMissing', {}, 'None')}</p>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="info-row">
|
|
98
|
+
<p class="label-sm">${t('ui.settings.envLastAudit', {}, 'Last audit')}</p>
|
|
99
|
+
<p class="value">${envState.lastAuditAt ? formatDate(envState.lastAuditAt, 'date') : '—'}</p>
|
|
100
|
+
</div>
|
|
101
|
+
<button class="btn btn-ghost btn-sm" type="button" id="sync-env-btn">
|
|
102
|
+
${icon('sync', 14)} ${t('ui.settings.envSync', {}, 'Sync env')}
|
|
103
|
+
</button>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function render() {
|
|
110
|
+
const payload = state.getPayload();
|
|
111
|
+
const control = payload?.control;
|
|
112
|
+
const runtime = payload?.runtime;
|
|
113
|
+
const docsDirty = payload?.docsDirty || [];
|
|
114
|
+
const envState = payload?.env;
|
|
116
115
|
|
|
117
116
|
return `
|
|
118
117
|
<div class="view-enter">
|
|
@@ -129,10 +128,10 @@ export async function render() {
|
|
|
129
128
|
<div style="display:flex;flex-direction:column;gap:var(--space-4)">
|
|
130
129
|
|
|
131
130
|
<!-- Info del proyecto activo -->
|
|
132
|
-
${control ? `
|
|
133
|
-
<div class="panel">
|
|
134
|
-
<div class="panel-header">
|
|
135
|
-
<p class="panel-title">Proyecto Activo</p>
|
|
131
|
+
${control ? `
|
|
132
|
+
<div class="glass-card panel">
|
|
133
|
+
<div class="panel-header">
|
|
134
|
+
<p class="panel-title">Proyecto Activo</p>
|
|
136
135
|
<span class="badge badge-success">Activo</span>
|
|
137
136
|
</div>
|
|
138
137
|
<div class="panel-body" style="display:flex;flex-direction:column;gap:var(--space-3)">
|
|
@@ -169,15 +168,15 @@ export async function render() {
|
|
|
169
168
|
</select>
|
|
170
169
|
</div>
|
|
171
170
|
</div>
|
|
172
|
-
</div>
|
|
173
|
-
</div>
|
|
174
|
-
` : `<p class="text-muted">${t('ui.settings.noProject', {}, 'No project loaded.')}</p>`}
|
|
175
|
-
|
|
176
|
-
${renderWorkspacePanel(payload)}
|
|
177
|
-
|
|
178
|
-
<!-- Estado del Repo -->
|
|
179
|
-
${runtime ? `
|
|
180
|
-
<div class="panel">
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
` : `<p class="text-muted">${t('ui.settings.noProject', {}, 'No project loaded.')}</p>`}
|
|
174
|
+
|
|
175
|
+
${renderWorkspacePanel(payload)}
|
|
176
|
+
|
|
177
|
+
<!-- Estado del Repo -->
|
|
178
|
+
${runtime ? `
|
|
179
|
+
<div class="glass-card panel">
|
|
181
180
|
<div class="panel-header">
|
|
182
181
|
<p class="panel-title">Repositorio</p>
|
|
183
182
|
<span class="badge badge-${runtime.clean ? 'success' : 'warning'}">${runtime.clean ? 'Limpio' : 'Con cambios'}</span>
|
|
@@ -214,51 +213,8 @@ export async function render() {
|
|
|
214
213
|
<!-- Col derecha -->
|
|
215
214
|
<div style="display:flex;flex-direction:column;gap:var(--space-4)">
|
|
216
215
|
|
|
217
|
-
<!-- Portfolio -->
|
|
218
|
-
<div class="panel">
|
|
219
|
-
<div class="panel-header">
|
|
220
|
-
<p class="panel-title">Portfolio de Proyectos</p>
|
|
221
|
-
<span class="badge badge-muted">${projects.length}</span>
|
|
222
|
-
</div>
|
|
223
|
-
<div class="panel-body" style="display:flex;flex-direction:column;gap:var(--space-3)">
|
|
224
|
-
${projects.length === 0
|
|
225
|
-
? '<div class="empty-state">Sin proyectos registrados.</div>'
|
|
226
|
-
: projects.map(p => `
|
|
227
|
-
<div class="project-row ${p.id === currentId ? 'is-active' : ''}">
|
|
228
|
-
<div class="project-row-info">
|
|
229
|
-
<p class="project-name">${esc(p.name)}</p>
|
|
230
|
-
<p class="project-path">${esc(p.root)}</p>
|
|
231
|
-
</div>
|
|
232
|
-
<div class="project-row-actions">
|
|
233
|
-
${p.available
|
|
234
|
-
? `<span class="badge badge-success">Disponible</span>`
|
|
235
|
-
: `<span class="badge badge-warning" title="No se puede cargar el control">No disponible</span>`}
|
|
236
|
-
${p.id === currentId
|
|
237
|
-
? `<span class="badge badge-accent">Activo</span>`
|
|
238
|
-
: p.available ? `<button class="btn btn-ghost btn-sm" type="button" data-switch="${esc(p.id)}">Abrir</button>` : ''
|
|
239
|
-
}
|
|
240
|
-
</div>
|
|
241
|
-
</div>
|
|
242
|
-
`).join('')
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
<!-- Registrar nuevo -->
|
|
246
|
-
<div style="margin-top:var(--space-2)">
|
|
247
|
-
<div class="field">
|
|
248
|
-
<label for="new-project-path">Registrar proyecto existente</label>
|
|
249
|
-
<input id="new-project-path" type="text" placeholder="/ruta/al/proyecto" />
|
|
250
|
-
</div>
|
|
251
|
-
<div class="form-actions" style="margin-top:var(--space-2)">
|
|
252
|
-
<button class="btn btn-ghost btn-sm" type="button" id="register-project-btn">
|
|
253
|
-
${icon('plus', 14)} Registrar
|
|
254
|
-
</button>
|
|
255
|
-
</div>
|
|
256
|
-
</div>
|
|
257
|
-
</div>
|
|
258
|
-
</div>
|
|
259
|
-
|
|
260
216
|
<!-- Docs Drift -->
|
|
261
|
-
<div class="panel">
|
|
217
|
+
<div class="glass-card panel">
|
|
262
218
|
<div class="panel-header">
|
|
263
219
|
<p class="panel-title">Documentación</p>
|
|
264
220
|
<span class="badge badge-${docsDirty.length ? 'warning' : 'success'}">${docsDirty.length ? `${docsDirty.length} desfasados` : 'Sincronizados'}</span>
|
|
@@ -274,17 +230,17 @@ export async function render() {
|
|
|
274
230
|
` : `
|
|
275
231
|
<p style="font-size:var(--text-sm);color:var(--success)">✓ Todos los archivos de documentación están sincronizados.</p>
|
|
276
232
|
`}
|
|
277
|
-
<button class="btn btn-ghost btn-sm" type="button" id="sync-docs-btn">
|
|
278
|
-
${icon('sync', 14)} Sincronizar ahora
|
|
279
|
-
</button>
|
|
280
|
-
</div>
|
|
281
|
-
</div>
|
|
282
|
-
|
|
283
|
-
${renderEnvPanel(envState)}
|
|
284
|
-
|
|
285
|
-
<!-- Milestones -->
|
|
286
|
-
${control?.milestones?.length ? `
|
|
287
|
-
<div class="panel">
|
|
233
|
+
<button class="btn btn-ghost btn-sm" type="button" id="sync-docs-btn">
|
|
234
|
+
${icon('sync', 14)} Sincronizar ahora
|
|
235
|
+
</button>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
${renderEnvPanel(envState)}
|
|
240
|
+
|
|
241
|
+
<!-- Milestones -->
|
|
242
|
+
${control?.milestones?.length ? `
|
|
243
|
+
<div class="glass-card panel">
|
|
288
244
|
<div class="panel-header">
|
|
289
245
|
<p class="panel-title">Milestones</p>
|
|
290
246
|
<span class="badge badge-accent">${control.milestones.length}</span>
|
|
@@ -303,60 +259,45 @@ export async function render() {
|
|
|
303
259
|
</div>
|
|
304
260
|
` : ''}
|
|
305
261
|
|
|
262
|
+
<!-- Skills & Extensions -->
|
|
263
|
+
<div class="glass-card panel">
|
|
264
|
+
<div class="panel-header">
|
|
265
|
+
<h3 class="panel-title">${icon('zap', 16)} ${t('ui.settings.skills', {}, 'Skills & Extensions')}</h3>
|
|
266
|
+
</div>
|
|
267
|
+
<div class="panel-body" id="settings-skills-container">
|
|
268
|
+
<p style="color:var(--text-muted);font-size:var(--text-sm)">${t('ui.settings.skillsLoading', {}, 'Loading skills...')}</p>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
306
272
|
</div>
|
|
307
273
|
</div>
|
|
308
274
|
</div>
|
|
309
275
|
`;
|
|
310
276
|
}
|
|
311
277
|
|
|
312
|
-
export function bindEvents() {
|
|
313
|
-
//
|
|
314
|
-
document.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
localStorage.setItem('ops-dashboard-project', id);
|
|
278
|
+
export async function bindEvents() {
|
|
279
|
+
// Sync docs
|
|
280
|
+
document.getElementById('sync-docs-btn')?.addEventListener('click', async () => {
|
|
281
|
+
try {
|
|
282
|
+
await api.syncDocs();
|
|
283
|
+
flash('Documentación sincronizada.', 'success');
|
|
319
284
|
window.dispatchEvent(new CustomEvent('ops:refresh'));
|
|
320
|
-
})
|
|
285
|
+
} catch (err) {
|
|
286
|
+
flash(err.message, 'error');
|
|
287
|
+
}
|
|
321
288
|
});
|
|
322
289
|
|
|
323
|
-
|
|
324
|
-
document.getElementById('register-project-btn')?.addEventListener('click', async () => {
|
|
325
|
-
const input = document.getElementById('new-project-path');
|
|
326
|
-
const root = input?.value.trim();
|
|
327
|
-
if (!root) { flash(t('ui.settings.enterPath', {}, 'Enter the project path.'), 'warning'); return; }
|
|
290
|
+
document.getElementById('sync-env-btn')?.addEventListener('click', async () => {
|
|
328
291
|
try {
|
|
329
|
-
await api.
|
|
330
|
-
flash('
|
|
331
|
-
if (input) input.value = '';
|
|
292
|
+
await api.syncEnv();
|
|
293
|
+
flash(t('ui.settings.envSynced', {}, 'Environment synced.'), 'success');
|
|
332
294
|
window.dispatchEvent(new CustomEvent('ops:refresh'));
|
|
333
295
|
} catch (err) {
|
|
334
296
|
flash(err.message, 'error');
|
|
335
297
|
}
|
|
336
298
|
});
|
|
337
299
|
|
|
338
|
-
|
|
339
|
-
document.getElementById('sync-docs-btn')?.addEventListener('click', async () => {
|
|
340
|
-
try {
|
|
341
|
-
await api.syncDocs();
|
|
342
|
-
flash('Documentación sincronizada.', 'success');
|
|
343
|
-
window.dispatchEvent(new CustomEvent('ops:refresh'));
|
|
344
|
-
} catch (err) {
|
|
345
|
-
flash(err.message, 'error');
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
document.getElementById('sync-env-btn')?.addEventListener('click', async () => {
|
|
350
|
-
try {
|
|
351
|
-
await api.syncEnv();
|
|
352
|
-
flash(t('ui.settings.envSynced', {}, 'Environment synced.'), 'success');
|
|
353
|
-
window.dispatchEvent(new CustomEvent('ops:refresh'));
|
|
354
|
-
} catch (err) {
|
|
355
|
-
flash(err.message, 'error');
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
document.getElementById('settings-locale-select')?.addEventListener('change', async e => {
|
|
300
|
+
document.getElementById('settings-locale-select')?.addEventListener('change', async e => {
|
|
360
301
|
const select = e.target;
|
|
361
302
|
const previousLocale = state.get('locale') || 'es';
|
|
362
303
|
const nextLocale = select.value;
|
|
@@ -376,6 +317,15 @@ export function bindEvents() {
|
|
|
376
317
|
select.disabled = false;
|
|
377
318
|
}
|
|
378
319
|
});
|
|
320
|
+
|
|
321
|
+
// Load skills into the container
|
|
322
|
+
try {
|
|
323
|
+
const skillsHtml = await skillsView.render();
|
|
324
|
+
const container = document.getElementById('settings-skills-container');
|
|
325
|
+
if (container) container.innerHTML = skillsHtml;
|
|
326
|
+
skillsView.bindEvents();
|
|
327
|
+
if (typeof skillsView.loadData === 'function') skillsView.loadData();
|
|
328
|
+
} catch (e) { console.warn('Skills load failed:', e); }
|
|
379
329
|
}
|
|
380
330
|
|
|
381
331
|
export { bindEvents as bind };
|
package/ui/js/views/sidebar.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* sidebar.js —
|
|
2
|
+
* sidebar.js — Glassmorphism sidebar with collapsed icon-strip layout
|
|
3
|
+
*
|
|
4
|
+
* 6 main nav items + 2 footer actions.
|
|
5
|
+
* CSS handles collapse/expand on hover; JS renders structure and manages badges.
|
|
3
6
|
*/
|
|
4
7
|
|
|
5
8
|
import { icon } from '../icons.js';
|
|
@@ -8,23 +11,24 @@ import * as consoleLogger from '../console-logger.js';
|
|
|
8
11
|
import * as onboarding from '../onboarding.js';
|
|
9
12
|
import { t } from '../i18n.js';
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
const NAV_ITEMS = [
|
|
15
|
+
{ id: 'projects', label: 'Projects', icon: 'folder' },
|
|
16
|
+
{ id: 'dashboard', label: 'Dashboard', icon: 'dashboard' },
|
|
17
|
+
{ id: 'tasks', label: 'Tasks', icon: 'tasks', badge: true },
|
|
18
|
+
{ id: 'timeline', label: 'Timeline', icon: 'timeline' },
|
|
19
|
+
{ id: 'terminal', label: 'Terminal', icon: 'terminal2' },
|
|
20
|
+
{ id: 'settings', label: 'Settings', icon: 'settings' },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const FOOTER_ITEMS = [
|
|
24
|
+
{ id: 'help', label: 'Help & Tour', icon: 'help', action: 'tour' },
|
|
25
|
+
{ id: 'console', label: 'Logs', icon: 'console', action: 'console', badge: 'error' },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/** Renders the full sidebar */
|
|
12
29
|
export function render() {
|
|
13
30
|
const el = document.getElementById('sidebar');
|
|
14
31
|
if (!el) return;
|
|
15
|
-
const navItems = [
|
|
16
|
-
{ id: 'overview', label: t('ui.nav.overview', {}, 'Overview'), icon: 'dashboard', section: 'menu' },
|
|
17
|
-
{ id: 'tasks', label: t('ui.nav.tasks', {}, 'Tasks'), icon: 'tasks', section: 'menu', badge: true },
|
|
18
|
-
{ id: 'board', label: t('ui.nav.board', {}, 'Board'), icon: 'board', section: 'menu' },
|
|
19
|
-
{ id: 'execution', label: t('ui.nav.execution', {}, 'Execution'), icon: 'execution', section: 'menu' },
|
|
20
|
-
{ id: 'skills', label: t('ui.nav.skills', {}, 'Skills'), icon: 'zap', section: 'menu' },
|
|
21
|
-
{ id: 'insights', label: t('ui.nav.insights', {}, 'Insights'), icon: 'insights', section: 'menu' },
|
|
22
|
-
];
|
|
23
|
-
const generalItems = [
|
|
24
|
-
{ id: 'settings', label: t('ui.nav.settings', {}, 'Settings'), icon: 'settings' },
|
|
25
|
-
{ id: 'help', label: t('ui.nav.help', {}, 'Help & Tour'), icon: 'help', action: 'tour' },
|
|
26
|
-
{ id: 'console', label: t('ui.nav.logs', {}, 'Logs'), icon: 'console', action: 'console', badge: 'error' },
|
|
27
|
-
];
|
|
28
32
|
|
|
29
33
|
el.innerHTML = `
|
|
30
34
|
<nav class="sidebar" aria-label="${t('ui.sidebar.aria', {}, 'Main navigation')}">
|
|
@@ -36,18 +40,17 @@ export function render() {
|
|
|
36
40
|
<span class="sidebar-logo-name">TrackOps</span>
|
|
37
41
|
</a>
|
|
38
42
|
|
|
39
|
-
<!--
|
|
43
|
+
<!-- Menu -->
|
|
40
44
|
<div class="sidebar-section">
|
|
41
45
|
<p class="sidebar-section-label">${t('ui.sidebar.menu', {}, 'Menu')}</p>
|
|
42
46
|
<ul class="sidebar-nav" role="list">
|
|
43
|
-
${
|
|
47
|
+
${NAV_ITEMS.map(item => _renderNavItem(item)).join('')}
|
|
44
48
|
</ul>
|
|
45
49
|
</div>
|
|
46
50
|
|
|
47
|
-
<!--
|
|
51
|
+
<!-- Footer -->
|
|
48
52
|
<div class="sidebar-footer">
|
|
49
|
-
|
|
50
|
-
${generalItems.map(item => _renderGeneralItem(item)).join('')}
|
|
53
|
+
${FOOTER_ITEMS.map(item => _renderFooterItem(item)).join('')}
|
|
51
54
|
</div>
|
|
52
55
|
</nav>
|
|
53
56
|
`;
|
|
@@ -58,37 +61,41 @@ export function render() {
|
|
|
58
61
|
function _renderNavItem(item) {
|
|
59
62
|
const activeView = state.get('activeView');
|
|
60
63
|
const isActive = activeView === item.id;
|
|
64
|
+
const label = t(`ui.nav.${item.id}`, {}, item.label);
|
|
61
65
|
const pendingCount = _getPendingCount(item);
|
|
62
66
|
|
|
63
67
|
return `
|
|
64
68
|
<li role="listitem">
|
|
65
69
|
<button
|
|
66
|
-
class="nav-item
|
|
70
|
+
class="nav-item${isActive ? ' is-active' : ''}"
|
|
67
71
|
data-view="${item.id}"
|
|
72
|
+
data-tooltip="${label}"
|
|
68
73
|
type="button"
|
|
69
74
|
aria-current="${isActive ? 'page' : 'false'}"
|
|
70
|
-
aria-label="${
|
|
75
|
+
aria-label="${label}${pendingCount ? `, ${t('ui.sidebar.pendingCount', { count: pendingCount }, `${pendingCount} pending`)}` : ''}"
|
|
71
76
|
>
|
|
72
77
|
<span class="nav-item-icon" aria-hidden="true">${icon(item.icon, 18)}</span>
|
|
73
|
-
<span class="nav-item-label">${
|
|
78
|
+
<span class="nav-item-label">${label}</span>
|
|
74
79
|
${pendingCount ? `<span class="nav-item-badge" aria-label="${t('ui.sidebar.tasksBadge', { count: pendingCount }, `${pendingCount} tasks`)}">${pendingCount}</span>` : ''}
|
|
75
80
|
</button>
|
|
76
81
|
</li>
|
|
77
82
|
`;
|
|
78
83
|
}
|
|
79
84
|
|
|
80
|
-
function
|
|
85
|
+
function _renderFooterItem(item) {
|
|
86
|
+
const label = t(`ui.nav.${item.id}`, {}, item.label);
|
|
87
|
+
|
|
81
88
|
return `
|
|
82
89
|
<button
|
|
83
90
|
class="nav-item"
|
|
84
|
-
data-action="${item.action
|
|
85
|
-
data-
|
|
91
|
+
data-action="${item.action}"
|
|
92
|
+
data-tooltip="${label}"
|
|
86
93
|
type="button"
|
|
87
|
-
aria-label="${
|
|
88
|
-
${item.
|
|
94
|
+
aria-label="${label}"
|
|
95
|
+
${item.id === 'console' ? 'id="sidebar-console-btn"' : ''}
|
|
89
96
|
>
|
|
90
97
|
<span class="nav-item-icon" aria-hidden="true">${icon(item.icon, 18)}</span>
|
|
91
|
-
<span class="nav-item-label">${
|
|
98
|
+
<span class="nav-item-label">${label}</span>
|
|
92
99
|
${item.badge === 'error' ? `<span class="nav-item-badge danger" id="sidebar-console-badge" aria-label="${t('ui.sidebar.errors', {}, 'errors')}" style="display:none">0</span>` : ''}
|
|
93
100
|
</button>
|
|
94
101
|
`;
|
|
@@ -103,7 +110,6 @@ function _getPendingCount(item) {
|
|
|
103
110
|
}
|
|
104
111
|
|
|
105
112
|
function _bindEvents(el) {
|
|
106
|
-
// Acciones especiales (no nav-view)
|
|
107
113
|
el.addEventListener('click', e => {
|
|
108
114
|
const btn = e.target.closest('[data-action]');
|
|
109
115
|
if (!btn) return;
|
|
@@ -117,7 +123,7 @@ function _bindEvents(el) {
|
|
|
117
123
|
});
|
|
118
124
|
}
|
|
119
125
|
|
|
120
|
-
/**
|
|
126
|
+
/** Refresh badges without full re-render */
|
|
121
127
|
export function updateBadges() {
|
|
122
128
|
const payload = state.getPayload();
|
|
123
129
|
if (!payload) return;
|