mdboard 1.3.0 → 2.1.0

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 (53) hide show
  1. package/bin.js +117 -59
  2. package/index.html +2161 -1579
  3. package/package.json +7 -5
  4. package/presets/kanban/api.json +91 -0
  5. package/presets/kanban/cli.json +69 -0
  6. package/presets/kanban/docs.json +29 -0
  7. package/presets/kanban/entities.json +128 -0
  8. package/presets/kanban/structure.json +15 -0
  9. package/presets/kanban/ui.json +86 -0
  10. package/presets/scrum/api.json +98 -0
  11. package/presets/scrum/cli.json +120 -0
  12. package/presets/scrum/docs.json +43 -0
  13. package/presets/scrum/entities.json +268 -0
  14. package/presets/scrum/structure.json +32 -0
  15. package/presets/scrum/ui.json +201 -0
  16. package/presets/shape-up/api.json +40 -0
  17. package/presets/shape-up/cli.json +44 -0
  18. package/presets/shape-up/docs.json +32 -0
  19. package/presets/shape-up/entities.json +140 -0
  20. package/presets/shape-up/structure.json +28 -0
  21. package/presets/shape-up/ui.json +114 -0
  22. package/src/cli/cli.js +186 -210
  23. package/src/cli/config.js +234 -0
  24. package/src/cli/init.js +128 -76
  25. package/src/cli/preset.js +849 -0
  26. package/src/cli/skill.js +417 -0
  27. package/src/cli/status.js +126 -96
  28. package/src/core/config.js +491 -38
  29. package/src/core/history.js +17 -1
  30. package/src/core/scanner.js +373 -463
  31. package/src/core/workspace.js +0 -15
  32. package/src/server/api.js +464 -741
  33. package/src/server/server.js +105 -130
  34. package/build.js +0 -44
  35. package/defaults.json +0 -43
  36. package/src/cli/sync.js +0 -194
  37. package/src/cli/theme.js +0 -142
  38. package/src/client/app.js +0 -266
  39. package/src/client/board.js +0 -157
  40. package/src/client/core.js +0 -331
  41. package/src/client/editor.js +0 -318
  42. package/src/client/history.js +0 -137
  43. package/src/client/metrics.js +0 -38
  44. package/src/client/milestones.js +0 -77
  45. package/src/client/notes.js +0 -183
  46. package/src/client/overview.js +0 -104
  47. package/src/client/panel.js +0 -637
  48. package/src/client/styles.css +0 -471
  49. package/src/client/table.js +0 -111
  50. package/src/client/template.html +0 -144
  51. package/src/client/themes.js +0 -261
  52. package/src/client/workspace.js +0 -164
  53. package/src/core/agent-scanner.js +0 -260
@@ -1,137 +0,0 @@
1
- /* ══════════════════════════════════════════════════════════════
2
- HISTORY — Project switch modal
3
- ══════════════════════════════════════════════════════════════ */
4
-
5
- function openHistoryModal() {
6
- var overlay = document.getElementById('history-overlay');
7
- var modal = document.getElementById('history-modal');
8
- if (!overlay || !modal) return;
9
-
10
- overlay.classList.add('open');
11
- modal.classList.add('open');
12
- modal.innerHTML = '<div class="history-header"><h2>Switch Project</h2><button class="panel-close" id="history-close">&times;</button></div><div class="history-body"><div class="history-loading">Loading...</div></div>';
13
-
14
- document.getElementById('history-close').onclick = closeHistoryModal;
15
- overlay.onclick = closeHistoryModal;
16
-
17
- fetchJson('/api/history').then(function(entries) {
18
- renderHistoryList(entries || []);
19
- });
20
- }
21
-
22
- function closeHistoryModal() {
23
- var overlay = document.getElementById('history-overlay');
24
- var modal = document.getElementById('history-modal');
25
- if (overlay) overlay.classList.remove('open');
26
- if (modal) modal.classList.remove('open');
27
- }
28
-
29
- function renderHistoryList(entries) {
30
- var modal = document.getElementById('history-modal');
31
- if (!modal) return;
32
-
33
- var body = modal.querySelector('.history-body');
34
- if (!body) return;
35
-
36
- if (entries.length === 0) {
37
- body.innerHTML = '<div class="history-empty">No projects in history yet.<br>Open mdboard from a project directory to add it.</div>';
38
- return;
39
- }
40
-
41
- // Sort: current first, then by lastOpened descending
42
- entries.sort(function(a, b) {
43
- if (a.isCurrent && !b.isCurrent) return -1;
44
- if (!a.isCurrent && b.isCurrent) return 1;
45
- return new Date(b.lastOpened) - new Date(a.lastOpened);
46
- });
47
-
48
- var html = '';
49
- for (var i = 0; i < entries.length; i++) {
50
- var e = entries[i];
51
- var initial = (e.name || 'P').charAt(0).toUpperCase();
52
- var relTime = timeAgo(e.lastOpened);
53
- var currentClass = e.isCurrent ? ' history-item-current' : '';
54
- var currentBadge = e.isCurrent ? '<span class="history-current-badge">Current</span>' : '';
55
-
56
- html += '<div class="history-item' + currentClass + '" data-path="' + escHtml(e.path) + '">' +
57
- '<div class="history-item-icon">' + escHtml(initial) + '</div>' +
58
- '<div class="history-item-info">' +
59
- '<div class="history-item-name">' + escHtml(e.name) + currentBadge + '</div>' +
60
- '<div class="history-item-path">' + escHtml(e.path) + '</div>' +
61
- '</div>' +
62
- '<div class="history-item-time">' + escHtml(relTime) + '</div>' +
63
- '</div>';
64
- }
65
-
66
- body.innerHTML = html;
67
-
68
- // Attach click handlers
69
- var items = body.querySelectorAll('.history-item');
70
- for (var j = 0; j < items.length; j++) {
71
- items[j].addEventListener('click', function() {
72
- var itemPath = this.getAttribute('data-path');
73
- if (this.classList.contains('history-item-current')) return;
74
- switchProject(itemPath);
75
- });
76
- }
77
- }
78
-
79
- function switchProject(projectPath) {
80
- var body = document.querySelector('#history-modal .history-body');
81
- if (body) body.innerHTML = '<div class="history-loading">Switching project...</div>';
82
-
83
- fetch('/api/history/switch', {
84
- method: 'POST',
85
- headers: { 'Content-Type': 'application/json' },
86
- body: JSON.stringify({ path: projectPath }),
87
- })
88
- .then(function(r) { return r.json(); })
89
- .then(function(data) {
90
- if (data && data.ok) {
91
- closeHistoryModal();
92
- // Reload everything — the SSE event will also trigger for other tabs
93
- D.activeSource = null;
94
- D.loaded = false;
95
- loadAll().then(function() {
96
- initFromConfig();
97
- if (hasWorkspace()) {
98
- buildSourceRail();
99
- D.activeSource = null;
100
- switchSource('overview');
101
- } else {
102
- renderAll();
103
- renderSidebarLogo();
104
- // Hide source rail if no workspace
105
- var rail = document.getElementById('source-rail');
106
- if (rail) rail.innerHTML = '';
107
- }
108
- });
109
- showToast('Switched to ' + (data.name || 'project'), 'success');
110
- } else {
111
- showToast('Error: ' + (data.error || 'Switch failed'), 'error');
112
- closeHistoryModal();
113
- }
114
- })
115
- .catch(function(err) {
116
- showToast('Error: ' + err.message, 'error');
117
- closeHistoryModal();
118
- });
119
- }
120
-
121
- function checkAutoOpenHistory() {
122
- if (D.config && D.config.hasProject === false) {
123
- openHistoryModal();
124
- }
125
- }
126
-
127
- function timeAgo(dateStr) {
128
- if (!dateStr) return '';
129
- var now = Date.now();
130
- var then = new Date(dateStr).getTime();
131
- var diff = Math.floor((now - then) / 1000);
132
- if (diff < 60) return 'just now';
133
- if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
134
- if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
135
- if (diff < 604800) return Math.floor(diff / 86400) + 'd ago';
136
- return new Date(dateStr).toLocaleDateString();
137
- }
@@ -1,38 +0,0 @@
1
- /* ══════════════════════════════════════════════════════════════
2
- METRICS VIEW
3
- ══════════════════════════════════════════════════════════════ */
4
- function renderMetrics() {
5
- var c = document.getElementById('metrics-container');
6
- var taskPlural = ENTITY_NAMES.task ? ENTITY_NAMES.task.plural : 'Tasks';
7
- if (!D.loaded) { c.innerHTML = '<div class="metrics-grid">' + '<div class="skeleton skeleton-card" style="height:200px"></div>'.repeat(4) + '</div>'; return; }
8
-
9
- var msProg = D.milestones.length ? D.milestones.map(function(ms) {
10
- var pct = ms.progress || 0;
11
- var clr = pct >= 100 ? 'var(--success)' : pct > 50 ? 'var(--warning)' : 'var(--accent)';
12
- return '<div class="health-row"><div class="health-dot" style="background:' + clr + '"></div>' + milestoneIcon(ms.status) + '<div class="health-label">' + escHtml(ms.title || ms.id || '') + '</div><div class="health-val">' + pct + '%</div></div>';
13
- }).join('') : '<div style="color:var(--text3);padding:8px 0">No milestones.</div>';
14
-
15
- var statuses = {};
16
- D.tasks.forEach(function(f) { var s = f.status || 'unknown'; statuses[s] = (statuses[s] || 0) + 1; });
17
- var statusHtml = Object.keys(statuses).map(function(s) {
18
- return '<div class="health-row">' + statusIcon(s) + '<div class="health-label" style="text-transform:capitalize">' + escHtml(STATUS_LABELS[s] || s) + '</div><div class="health-val">' + statuses[s] + '</div></div>';
19
- }).join('') || '<div style="color:var(--text3);padding:8px 0">No data.</div>';
20
-
21
- var priorities = {};
22
- D.tasks.forEach(function(f) { var p = f.priority || 'none'; priorities[p] = (priorities[p] || 0) + 1; });
23
- var priorityHtml = Object.keys(priorities).map(function(p) {
24
- return '<div class="health-row">' + priorityIcon(p) + '<div class="health-label" style="text-transform:capitalize">' + escHtml(PRIORITY_LABELS[p] || p) + '</div><div class="health-val">' + priorities[p] + '</div></div>';
25
- }).join('') || '<div style="color:var(--text3);padding:8px 0">No data.</div>';
26
-
27
- var h = D.health || {};
28
- var qualHtml = '<div class="health-row"><div class="health-dot" style="background:var(--text2)"></div><div class="health-label">Total ' + escHtml(taskPlural) + '</div><div class="health-val">' + (h.totalFeatures || D.tasks.length) + '</div></div>' +
29
- '<div class="health-row"><div class="health-dot" style="background:var(--success)"></div><div class="health-label">Completed</div><div class="health-val">' + (h.completedFeatures || 0) + '</div></div>' +
30
- '<div class="health-row"><div class="health-dot" style="background:var(--warning)"></div><div class="health-label">In Progress</div><div class="health-val">' + (h.inProgressFeatures || 0) + '</div></div>' +
31
- '<div class="health-row"><div class="health-dot" style="background:var(--accent)"></div><div class="health-label">Avg Velocity</div><div class="health-val">' + (h.velocity != null ? h.velocity + '%' : 'N/A') + '</div></div>';
32
-
33
- c.innerHTML = '<div class="metrics-grid">' +
34
- '<div class="metric-card"><h3>Milestone Progress</h3>' + msProg + '</div>' +
35
- '<div class="metric-card"><h3>Status Breakdown</h3>' + statusHtml + '</div>' +
36
- '<div class="metric-card"><h3>Priority Breakdown</h3>' + priorityHtml + '</div>' +
37
- '<div class="metric-card"><h3>Project Health</h3>' + qualHtml + '</div></div>';
38
- }
@@ -1,77 +0,0 @@
1
- /* ══════════════════════════════════════════════════════════════
2
- MILESTONES VIEW
3
- ══════════════════════════════════════════════════════════════ */
4
- var msFilter = { status: '' };
5
-
6
- function renderMsFilters() {
7
- var c = document.getElementById('ms-filters');
8
- var msStatuses = (D.config && D.config.statuses && D.config.statuses.milestone) ?
9
- D.config.statuses.milestone.map(function(s) { return s; }) :
10
- [{key:'planned',label:'Planned'},{key:'active',label:'Active'},{key:'completed',label:'Completed'}];
11
-
12
- c.innerHTML = '<label>Filter:</label>' +
13
- '<select data-msf="status"><option value="">All Statuses</option>' +
14
- msStatuses.map(function(s) {
15
- return '<option value="' + s.key + '"' + (msFilter.status === s.key ? ' selected' : '') + '>' + escHtml(s.label) + '</option>';
16
- }).join('') + '</select>';
17
- c.querySelector('select[data-msf]').addEventListener('change', function(e) { msFilter.status = e.target.value; renderMilestones(); });
18
- }
19
-
20
- function renderMilestones() {
21
- var c = document.getElementById('milestones-container');
22
- var taskPlural = ENTITY_NAMES.task ? ENTITY_NAMES.task.plural.toLowerCase() : 'tasks';
23
- var msDir = ENTITY_NAMES.milestone ? ENTITY_NAMES.milestone.dir : 'milestones';
24
- var msPlural = ENTITY_NAMES.milestone ? ENTITY_NAMES.milestone.plural.toLowerCase() : 'milestones';
25
- if (!D.loaded) { c.innerHTML = '<div class="loading-container"><div class="skeleton skeleton-card" style="height:200px"></div></div>'; return; }
26
-
27
- var milestones = D.milestones;
28
- if (msFilter.status) milestones = milestones.filter(function(m) { return m.status === msFilter.status; });
29
-
30
- if (!milestones.length) {
31
- c.innerHTML = '<div class="empty"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" y1="22" x2="4" y2="15"/></svg><p>' + (D.milestones.length ? 'No ' + escHtml(msPlural) + ' match filter.' : 'No ' + escHtml(msPlural) + ' yet. Create milestone directories under project/' + escHtml(msDir) + '/ to get started.') + '</p></div>';
32
- return;
33
- }
34
-
35
- c.innerHTML = milestones.map(function(ms) {
36
- var pct = ms.progress || 0;
37
- var fc = ms.featureCount || 0, cc = ms.completedCount || 0;
38
- var msEpics = D.epics.filter(function(e) { return e.milestone === (ms.id || ms._dir); });
39
- var epicCards = msEpics.length ? msEpics.map(function(e) {
40
- var ep = e.progress || 0;
41
- var ec = epicColor(e.id || e.title || '');
42
- return '<div class="ms-epic" data-epic-id="' + escHtml(e.id || '') + '"><div class="epic-name"><span class="dot" style="background:' + ec + '"></span>' + escHtml(e.title || e.id || '') + '</div>' +
43
- '<div class="epic-counts">' + (e.completedCount || 0) + ' / ' + (e.featureCount || 0) + ' ' + escHtml(taskPlural) + ' &middot; ' + (e.totalPoints || 0) + ' pts</div>' +
44
- '<div class="progress progress-accent"><div class="progress-fill" style="width:' + ep + '%;background:' + ec + '"></div></div>' +
45
- '<div class="epic-meta">' +
46
- (e.status ? '<span class="badge ' + badgeClass('badge', e.status) + '">' + statusIcon(e.status) + ' ' + escHtml(e.status) + '</span>' : '') +
47
- (e.priority ? '<span class="badge ' + badgeClass('badge', e.priority) + '">' + priorityIcon(e.priority) + ' ' + escHtml(e.priority) + '</span>' : '') +
48
- '</div></div>';
49
- }).join('') : '<div style="color:var(--text3);font-size:.82rem">No epics yet.</div>';
50
-
51
- return '<div class="ms-card" data-ms-id="' + escHtml(ms.id || '') + '">' +
52
- '<div class="ms-header"><h2>' + milestoneIcon(ms.status) + ' ' + escHtml(ms.title || ms.id || '') + '</h2>' +
53
- '<span class="badge ' + badgeClass('badge', ms.status) + '">' + escHtml(ms.status || '') + '</span>' +
54
- (ms.deadline ? '<span class="ms-deadline">' + fmtDate(ms.deadline) + '</span>' : '') + '</div>' +
55
- '<div class="ms-progress"><div class="progress-label"><span>' + cc + ' / ' + fc + ' ' + escHtml(taskPlural) + '</span><span>' + pct + '%</span></div>' +
56
- '<div class="progress progress-lg progress-success"><div class="progress-fill" style="width:' + pct + '%"></div></div></div>' +
57
- '<div class="ms-epics">' + epicCards + '</div></div>';
58
- }).join('');
59
-
60
- // Click handlers for milestones
61
- c.querySelectorAll('.ms-card[data-ms-id]').forEach(function(card) {
62
- card.addEventListener('click', function(e) {
63
- if (e.target.closest('.ms-epic')) return;
64
- var ms = D.milestones.find(function(m) { return m.id === card.dataset.msId; });
65
- if (ms) openPanel('milestones', ms);
66
- });
67
- });
68
-
69
- // Click handlers for epics
70
- c.querySelectorAll('.ms-epic[data-epic-id]').forEach(function(card) {
71
- card.addEventListener('click', function(e) {
72
- e.stopPropagation();
73
- var epic = D.epics.find(function(ep) { return ep.id === card.dataset.epicId; });
74
- if (epic) openPanel('epics', epic);
75
- });
76
- });
77
- }
@@ -1,183 +0,0 @@
1
- /* ══════════════════════════════════════════════════════════════
2
- NOTES VIEW — Notion-style split layout
3
- ══════════════════════════════════════════════════════════════ */
4
-
5
- var notesState = { notes: [], activeId: null, editor: null, dirty: false };
6
-
7
- function renderNotes() {
8
- var container = document.getElementById('notes-container');
9
- if (!container) return;
10
-
11
- container.innerHTML =
12
- '<div class="notes-layout">' +
13
- '<div class="notes-sidebar">' +
14
- '<div class="notes-toolbar">' +
15
- '<input type="text" placeholder="Search..." id="notes-search">' +
16
- '<button class="btn btn-sm btn-create" id="notes-new">+ New</button>' +
17
- '</div>' +
18
- '<div class="notes-list" id="notes-list"></div>' +
19
- '</div>' +
20
- '<div class="notes-editor" id="notes-editor-pane">' +
21
- '<div class="notes-empty-state">' +
22
- '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="width:48px;height:48px;opacity:.3;margin-bottom:12px"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>' +
23
- '<p>Select a note or create a new one</p>' +
24
- '<p style="font-size:12px;margin-top:4px;color:var(--text3)">Use <kbd>/</kbd> for block commands</p>' +
25
- '</div>' +
26
- '</div>' +
27
- '</div>';
28
-
29
- loadNoteList();
30
-
31
- document.getElementById('notes-new').addEventListener('click', createNewNote);
32
- document.getElementById('notes-search').addEventListener('input', function() {
33
- renderNoteList(this.value.toLowerCase());
34
- });
35
- }
36
-
37
- async function loadNoteList() {
38
- var base = apiBase();
39
- var notes = await fetchJson(base + '/notes');
40
- notesState.notes = notes || [];
41
- renderNoteList('');
42
- }
43
-
44
- function renderNoteList(filter) {
45
- var list = document.getElementById('notes-list');
46
- if (!list) return;
47
-
48
- var notes = notesState.notes;
49
- if (filter) {
50
- notes = notes.filter(function(n) {
51
- return (n.title || '').toLowerCase().indexOf(filter) !== -1;
52
- });
53
- }
54
-
55
- notes.sort(function(a, b) {
56
- return (b.updated || b.created || '').localeCompare(a.updated || a.created || '');
57
- });
58
-
59
- if (notes.length === 0) {
60
- list.innerHTML = '<div class="notes-list-empty">No notes yet</div>';
61
- return;
62
- }
63
-
64
- var html = '';
65
- for (var i = 0; i < notes.length; i++) {
66
- var n = notes[i];
67
- var active = n.id === notesState.activeId ? ' active' : '';
68
- html += '<div class="notes-list-item' + active + '" data-id="' + escHtml(n.id) + '">' +
69
- '<div class="notes-list-title">' + escHtml(n.title || 'Untitled') + '</div>' +
70
- '<div class="notes-list-date">' + escHtml(n.updated || n.created || '') + '</div>' +
71
- '</div>';
72
- }
73
- list.innerHTML = html;
74
-
75
- list.querySelectorAll('.notes-list-item').forEach(function(el) {
76
- el.addEventListener('click', function() {
77
- selectNote(el.dataset.id);
78
- });
79
- });
80
- }
81
-
82
- async function selectNote(id) {
83
- if (notesState.editor) {
84
- destroyEditor(notesState.editor);
85
- notesState.editor = null;
86
- }
87
-
88
- notesState.activeId = id;
89
- renderNoteList(document.getElementById('notes-search') ? document.getElementById('notes-search').value.toLowerCase() : '');
90
-
91
- var pane = document.getElementById('notes-editor-pane');
92
- if (!pane) return;
93
-
94
- pane.innerHTML = '<div class="notes-editor-loading">Loading...</div>';
95
-
96
- var base = apiBase();
97
- var note = await fetchJson(base + '/notes/' + encodeURIComponent(id));
98
- if (!note) {
99
- pane.innerHTML = '<div class="notes-empty-state"><p>Note not found</p></div>';
100
- return;
101
- }
102
-
103
- pane.innerHTML =
104
- '<div class="notes-header-bar">' +
105
- '<input type="text" class="notes-title-input" id="notes-title" placeholder="Untitled" value="' + escHtml(note.title || '') + '">' +
106
- '<span class="notes-meta">' + escHtml(note.updated || note.created || '') + '</span>' +
107
- '</div>' +
108
- '<div class="notes-editorjs-wrap" id="notes-editorjs"></div>' +
109
- '<div class="notes-editor-footer">' +
110
- '<button class="btn btn-danger btn-sm" id="notes-delete">Delete</button>' +
111
- '<span style="flex:1"></span>' +
112
- '<button class="btn btn-primary" id="notes-save">Save</button>' +
113
- '</div>';
114
-
115
- notesState.editor = initEditor('notes-editorjs', note.content || '', {
116
- placeholder: 'Type / for commands...',
117
- autofocus: true,
118
- onChange: function() { notesState.dirty = true; }
119
- });
120
- notesState.dirty = false;
121
-
122
- document.getElementById('notes-save').addEventListener('click', saveCurrentNote);
123
- document.getElementById('notes-delete').addEventListener('click', function() {
124
- deleteCurrentNote(id);
125
- });
126
-
127
- // Ctrl/Cmd+S to save
128
- pane.addEventListener('keydown', function(e) {
129
- if ((e.metaKey || e.ctrlKey) && e.key === 's') {
130
- e.preventDefault();
131
- saveCurrentNote();
132
- }
133
- });
134
- }
135
-
136
- async function saveCurrentNote() {
137
- if (!notesState.activeId) return;
138
-
139
- var titleEl = document.getElementById('notes-title');
140
- var title = titleEl ? titleEl.value : '';
141
- var content = notesState.editor ? await getEditorMarkdown(notesState.editor) : '';
142
-
143
- var updates = {};
144
- var current = notesState.notes.find(function(n) { return n.id === notesState.activeId; });
145
- if (title && (!current || title !== current.title)) updates.title = title;
146
- updates.content = content;
147
- updates.updated = new Date().toISOString().split('T')[0];
148
-
149
- var ok = await patchItem('notes', notesState.activeId, updates);
150
- if (ok) {
151
- notesState.dirty = false;
152
- await loadNoteList();
153
- }
154
- }
155
-
156
- async function createNewNote() {
157
- var result = await createItem('notes', { title: 'Untitled', content: '' });
158
- if (result && result.id) {
159
- await loadNoteList();
160
- selectNote(result.id);
161
- }
162
- }
163
-
164
- async function deleteCurrentNote(id) {
165
- if (!confirm('Delete this note?')) return;
166
- var ok = await deleteItem('notes', id);
167
- if (ok) {
168
- if (notesState.editor) {
169
- destroyEditor(notesState.editor);
170
- notesState.editor = null;
171
- }
172
- notesState.activeId = null;
173
- await loadNoteList();
174
- var pane = document.getElementById('notes-editor-pane');
175
- if (pane) {
176
- pane.innerHTML =
177
- '<div class="notes-empty-state">' +
178
- '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="width:48px;height:48px;opacity:.3;margin-bottom:12px"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>' +
179
- '<p>Select a note or create a new one</p>' +
180
- '</div>';
181
- }
182
- }
183
- }
@@ -1,104 +0,0 @@
1
- /* ══════════════════════════════════════════════════════════════
2
- OVERVIEW VIEW
3
- ══════════════════════════════════════════════════════════════ */
4
- async function renderOverview() {
5
- var c = document.getElementById('overview-container');
6
- if (!D.loaded) { c.innerHTML = '<div class="loading-container"><div class="skeleton skeleton-card" style="height:200px"></div></div>'; return; }
7
-
8
- // Fetch overview data
9
- var overviewMs = await fetchJson('/api/overview/milestones') || [];
10
- var overviewLinks = await fetchJson('/api/overview/links');
11
- var overviewMetrics = await fetchJson('/api/overview/metrics');
12
-
13
- D.overviewLinks = overviewLinks;
14
-
15
- var html = '<h2 style="margin-bottom:16px;font-size:16px;font-weight:700">Workspace Overview</h2>';
16
-
17
- // Global Milestones
18
- html += '<h3 style="font-size:13px;text-transform:uppercase;letter-spacing:.04em;color:var(--text2);font-weight:600;margin:16px 0 8px">Global Milestones</h3>';
19
- if (overviewMs.length > 0) {
20
- html += overviewMs.map(function(ms) {
21
- var pct = ms.combinedProgress != null ? ms.combinedProgress : (ms.progress || 0);
22
- var tracked = ms.tracked || [];
23
- var trackedHtml = '';
24
- if (tracked.length > 0) {
25
- trackedHtml = '<div class="tracked-ms"><div class="tracked-ms-header">Tracked Sub-Milestones</div>' +
26
- tracked.map(function(t) {
27
- return '<div class="tracked-ms-item"><span class="dot" style="background:' + (t.sourceColor || 'var(--accent)') + '"></span>' +
28
- '<span style="font-size:12px;flex:1">' + escHtml(t.title || t.id || '') + '</span>' +
29
- '<div class="progress progress-accent" style="width:80px"><div class="progress-fill" style="width:' + t.progress + '%;background:' + (t.sourceColor || 'var(--accent)') + '"></div></div>' +
30
- '<span class="tracked-ms-pct">' + t.progress + '%</span></div>';
31
- }).join('') + '</div>';
32
- }
33
- return '<div class="ms-card">' +
34
- '<div class="ms-header"><h2>' + milestoneIcon(ms.status) + ' ' + escHtml(ms.title || ms.id || '') + '</h2>' +
35
- '<span class="badge ' + badgeClass('badge', ms.status) + '">' + escHtml(ms.status || '') + '</span>' +
36
- (ms.deadline ? '<span class="ms-deadline">' + fmtDate(ms.deadline) + '</span>' : '') + '</div>' +
37
- '<div class="ms-progress"><div class="progress-label"><span>' + (ms.completedCount || 0) + ' / ' + (ms.featureCount || 0) + ' tasks</span><span>' + pct + '%</span></div>' +
38
- '<div class="progress progress-lg progress-success"><div class="progress-fill" style="width:' + pct + '%"></div></div></div>' +
39
- trackedHtml + '</div>';
40
- }).join('');
41
- } else {
42
- html += '<div style="color:var(--text3);padding:8px 0">No milestones found across sources.</div>';
43
- }
44
-
45
- // Cross-project links
46
- if (overviewLinks && overviewLinks.links && overviewLinks.links.length > 0) {
47
- html += '<h3 style="font-size:13px;text-transform:uppercase;letter-spacing:.04em;color:var(--text2);font-weight:600;margin:24px 0 8px">Cross-Project Links</h3>';
48
- html += '<div class="metrics-grid">';
49
- var linkGroups = {};
50
- overviewLinks.links.forEach(function(l) {
51
- var key = (l.fromSource || 'unknown');
52
- if (!linkGroups[key]) linkGroups[key] = [];
53
- linkGroups[key].push(l);
54
- });
55
- Object.keys(linkGroups).forEach(function(src) {
56
- var links = linkGroups[src];
57
- html += '<div class="metric-card"><h3>' + escHtml(src) + ' Links</h3>';
58
- html += links.map(function(l) {
59
- return '<div class="health-row"><span style="font-size:12px;font-family:var(--mono)">' + escHtml(l.from || '') + '</span>' +
60
- '<span style="color:var(--text3);margin:0 4px">&rarr;</span>' +
61
- '<span class="link-chip" data-link="' + escHtml(l.to || '') + '">' +
62
- '<span class="link-chip-dot" style="background:var(--accent)"></span>' + escHtml(l.to || '') + '</span></div>';
63
- }).join('');
64
- html += '</div>';
65
- });
66
- html += '</div>';
67
- }
68
-
69
- // Source metrics
70
- if (overviewMetrics && overviewMetrics.sources) {
71
- html += '<h3 style="font-size:13px;text-transform:uppercase;letter-spacing:.04em;color:var(--text2);font-weight:600;margin:24px 0 8px">Source Metrics</h3>';
72
- html += '<div class="metrics-grid">';
73
- Object.keys(overviewMetrics.sources).forEach(function(key) {
74
- var m = overviewMetrics.sources[key];
75
- var pct = m.totalTasks > 0 ? Math.round((m.completedTasks / m.totalTasks) * 100) : 0;
76
- html += '<div class="metric-card" style="border-left:3px solid ' + (m.color || 'var(--accent)') + '">' +
77
- '<h3>' + escHtml(m.label || key) + '</h3>' +
78
- '<div class="health-row"><div class="health-label">Tasks</div><div class="health-val">' + m.totalTasks + '</div></div>' +
79
- '<div class="health-row"><div class="health-label">Completed</div><div class="health-val">' + m.completedTasks + '</div></div>' +
80
- '<div class="health-row"><div class="health-label">Points</div><div class="health-val">' + m.totalPoints + '</div></div>' +
81
- '<div class="progress progress-lg progress-success" style="margin-top:8px"><div class="progress-fill" style="width:' + pct + '%;background:' + (m.color || 'var(--success)') + '"></div></div>' +
82
- '</div>';
83
- });
84
- html += '</div>';
85
- }
86
-
87
- c.innerHTML = html;
88
-
89
- // Click handlers for link chips
90
- c.querySelectorAll('.link-chip[data-link]').forEach(function(chip) {
91
- chip.addEventListener('click', function() {
92
- var ref = chip.dataset.link;
93
- var parts = ref.split(':');
94
- if (parts.length === 2) {
95
- switchSource(parts[0]);
96
- // After switch, try to find and open the item
97
- setTimeout(function() {
98
- var task = D.tasks.find(function(t) { return t.id === ref || t.id === parts[1]; });
99
- if (task) openPanel('tasks', task);
100
- }, 500);
101
- }
102
- });
103
- });
104
- }