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
package/src/cli/theme.js DELETED
@@ -1,142 +0,0 @@
1
- /**
2
- * mdboard theme — Set or list available themes
3
- */
4
-
5
- const fs = require('fs');
6
- const path = require('path');
7
- const os = require('os');
8
-
9
- const THEME_IDS = [
10
- 'default-dark',
11
- 'linear-dark', 'linear-light',
12
- 'jira-dark', 'jira-light',
13
- 'catppuccin-mocha', 'catppuccin-latte',
14
- 'ayu-dark', 'ayu-light',
15
- 'tokyo-night', 'tokyo-night-storm',
16
- 'notion-light', 'notion-dark',
17
- 'github-dark', 'github-light',
18
- 'dracula', 'nord',
19
- 'solarized-dark', 'solarized-light',
20
- 'one-dark',
21
- 'gruvbox-dark',
22
- ];
23
-
24
- const THEME_NAMES = {
25
- 'default-dark': 'Default Dark',
26
- 'linear-dark': 'Linear Dark', 'linear-light': 'Linear Light',
27
- 'jira-dark': 'Jira Dark', 'jira-light': 'Jira Light',
28
- 'catppuccin-mocha': 'Catppuccin Mocha', 'catppuccin-latte': 'Catppuccin Latte',
29
- 'ayu-dark': 'Ayu Dark', 'ayu-light': 'Ayu Light',
30
- 'tokyo-night': 'Tokyo Night', 'tokyo-night-storm': 'Tokyo Night Storm',
31
- 'notion-light': 'Notion Light', 'notion-dark': 'Notion Dark',
32
- 'github-dark': 'GitHub Dark', 'github-light': 'GitHub Light',
33
- 'dracula': 'Dracula', 'nord': 'Nord',
34
- 'solarized-dark': 'Solarized Dark', 'solarized-light': 'Solarized Light',
35
- 'one-dark': 'One Dark',
36
- 'gruvbox-dark': 'Gruvbox Dark',
37
- };
38
-
39
- const THEME_VARS = {
40
- 'default-dark': {bg:'#0A0A0B',surface:'#141415',surface2:'#1A1A1C',border:'#232326',border2:'#2E2E33',text:'#E8E8ED',text2:'#8B8B93',text3:'#5A5A63',accent:'#5B6EF5','accent-dim':'rgba(91,110,245,.15)',success:'#2EA043','success-dim':'rgba(46,160,67,.15)',warning:'#D4A72C','warning-dim':'rgba(212,167,44,.15)',danger:'#DA3633','danger-dim':'rgba(218,54,51,.15)',purple:'#8B5CF6','purple-dim':'rgba(139,92,246,.15)'},
41
- 'linear-dark': {bg:'#101012',surface:'#1A1A1F',surface2:'#222228',border:'#2C2C35',border2:'#38384A',text:'#E2E2E9',text2:'#8E8E9E',text3:'#5C5C6E',accent:'#5E6AD2','accent-dim':'rgba(94,106,210,.15)',success:'#4DA771','success-dim':'rgba(77,167,113,.15)',warning:'#F2C94C','warning-dim':'rgba(242,201,76,.15)',danger:'#EB5757','danger-dim':'rgba(235,87,87,.15)',purple:'#BB87FC','purple-dim':'rgba(187,135,252,.15)'},
42
- 'linear-light': {bg:'#F9F9FB',surface:'#FFFFFF',surface2:'#F0F0F4',border:'#E0E0E6',border2:'#D0D0D8',text:'#1A1A2E',text2:'#6B6B80',text3:'#9090A0',accent:'#5E6AD2','accent-dim':'rgba(94,106,210,.1)',success:'#4DA771','success-dim':'rgba(77,167,113,.1)',warning:'#D4A017','warning-dim':'rgba(212,160,23,.1)',danger:'#EB5757','danger-dim':'rgba(235,87,87,.1)',purple:'#9B6DD7','purple-dim':'rgba(155,109,215,.1)'},
43
- 'jira-dark': {bg:'#0D1117',surface:'#161B22',surface2:'#1C2333',border:'#293040',border2:'#344050',text:'#DEE4EC',text2:'#8C96A5',text3:'#5A6577',accent:'#2684FF','accent-dim':'rgba(38,132,255,.15)',success:'#36B37E','success-dim':'rgba(54,179,126,.15)',warning:'#FFAB00','warning-dim':'rgba(255,171,0,.15)',danger:'#FF5630','danger-dim':'rgba(255,86,48,.15)',purple:'#6554C0','purple-dim':'rgba(101,84,192,.15)'},
44
- 'jira-light': {bg:'#F4F5F7',surface:'#FFFFFF',surface2:'#EBECF0',border:'#DFE1E6',border2:'#C1C7D0',text:'#172B4D',text2:'#6B778C',text3:'#97A0AF',accent:'#0052CC','accent-dim':'rgba(0,82,204,.1)',success:'#36B37E','success-dim':'rgba(54,179,126,.1)',warning:'#FF991F','warning-dim':'rgba(255,153,31,.1)',danger:'#FF5630','danger-dim':'rgba(255,86,48,.1)',purple:'#6554C0','purple-dim':'rgba(101,84,192,.1)'},
45
- 'catppuccin-mocha': {bg:'#1E1E2E',surface:'#262637',surface2:'#313244',border:'#3B3B52',border2:'#45475A',text:'#CDD6F4',text2:'#A6ADC8',text3:'#6C7086',accent:'#89B4FA','accent-dim':'rgba(137,180,250,.15)',success:'#A6E3A1','success-dim':'rgba(166,227,161,.15)',warning:'#F9E2AF','warning-dim':'rgba(249,226,175,.15)',danger:'#F38BA8','danger-dim':'rgba(243,139,168,.15)',purple:'#CBA6F7','purple-dim':'rgba(203,166,247,.15)'},
46
- 'catppuccin-latte': {bg:'#EFF1F5',surface:'#FFFFFF',surface2:'#E6E9EF',border:'#CCD0DA',border2:'#BCC0CC',text:'#4C4F69',text2:'#6C6F85',text3:'#9CA0B0',accent:'#1E66F5','accent-dim':'rgba(30,102,245,.1)',success:'#40A02B','success-dim':'rgba(64,160,43,.1)',warning:'#DF8E1D','warning-dim':'rgba(223,142,29,.1)',danger:'#D20F39','danger-dim':'rgba(210,15,57,.1)',purple:'#8839EF','purple-dim':'rgba(136,57,239,.1)'},
47
- 'ayu-dark': {bg:'#0B0E14',surface:'#0F1219',surface2:'#151920',border:'#1E222A',border2:'#272D38',text:'#BFBDB6',text2:'#6C7380',text3:'#4A5060',accent:'#E6B450','accent-dim':'rgba(230,180,80,.15)',success:'#7FD962','success-dim':'rgba(127,217,98,.15)',warning:'#FFB454','warning-dim':'rgba(255,180,84,.15)',danger:'#F07178','danger-dim':'rgba(240,113,120,.15)',purple:'#D2A6FF','purple-dim':'rgba(210,166,255,.15)'},
48
- 'ayu-light': {bg:'#F8F9FA',surface:'#FFFFFF',surface2:'#F0F1F2',border:'#E0E1E2',border2:'#D4D5D6',text:'#5C6166',text2:'#787B80',text3:'#ACB0B5',accent:'#FF9940','accent-dim':'rgba(255,153,64,.1)',success:'#6CBF43','success-dim':'rgba(108,191,67,.1)',warning:'#F2AE49','warning-dim':'rgba(242,174,73,.1)',danger:'#F07171','danger-dim':'rgba(240,113,113,.1)',purple:'#A37ACC','purple-dim':'rgba(163,122,204,.1)'},
49
- 'tokyo-night': {bg:'#1A1B26',surface:'#1F2335',surface2:'#24283B',border:'#2F3449',border2:'#3B4261',text:'#C0CAF5',text2:'#7982A9',text3:'#565F89',accent:'#7AA2F7','accent-dim':'rgba(122,162,247,.15)',success:'#9ECE6A','success-dim':'rgba(158,206,106,.15)',warning:'#E0AF68','warning-dim':'rgba(224,175,104,.15)',danger:'#F7768E','danger-dim':'rgba(247,118,142,.15)',purple:'#BB9AF7','purple-dim':'rgba(187,154,247,.15)'},
50
- 'tokyo-night-storm': {bg:'#24283B',surface:'#292E42',surface2:'#2F3549',border:'#3B4261',border2:'#464F78',text:'#C0CAF5',text2:'#7982A9',text3:'#565F89',accent:'#7AA2F7','accent-dim':'rgba(122,162,247,.15)',success:'#9ECE6A','success-dim':'rgba(158,206,106,.15)',warning:'#E0AF68','warning-dim':'rgba(224,175,104,.15)',danger:'#F7768E','danger-dim':'rgba(247,118,142,.15)',purple:'#BB9AF7','purple-dim':'rgba(187,154,247,.15)'},
51
- 'notion-light': {bg:'#F7F6F3',surface:'#FFFFFF',surface2:'#EEEEEC',border:'#E3E2DE',border2:'#D4D3CF',text:'#37352F',text2:'#6B6B6B',text3:'#9B9A97',accent:'#2383E2','accent-dim':'rgba(35,131,226,.1)',success:'#0F7B0F','success-dim':'rgba(15,123,15,.1)',warning:'#CB7B00','warning-dim':'rgba(203,123,0,.1)',danger:'#E03E3E','danger-dim':'rgba(224,62,62,.1)',purple:'#6940A5','purple-dim':'rgba(105,64,165,.1)'},
52
- 'notion-dark': {bg:'#191919',surface:'#202020',surface2:'#2B2B2B',border:'#363636',border2:'#444444',text:'#E3E2DE',text2:'#9B9B9B',text3:'#6B6B6B',accent:'#529CCA','accent-dim':'rgba(82,156,202,.15)',success:'#4DAB9A','success-dim':'rgba(77,171,154,.15)',warning:'#CB7B00','warning-dim':'rgba(203,123,0,.15)',danger:'#E03E3E','danger-dim':'rgba(224,62,62,.15)',purple:'#9A6DD7','purple-dim':'rgba(154,109,215,.15)'},
53
- 'github-dark': {bg:'#0D1117',surface:'#161B22',surface2:'#1C2128',border:'#30363D',border2:'#3D444D',text:'#E6EDF3',text2:'#8B949E',text3:'#6E7681',accent:'#58A6FF','accent-dim':'rgba(88,166,255,.15)',success:'#3FB950','success-dim':'rgba(63,185,80,.15)',warning:'#D29922','warning-dim':'rgba(210,153,34,.15)',danger:'#F85149','danger-dim':'rgba(248,81,73,.15)',purple:'#BC8CFF','purple-dim':'rgba(188,140,255,.15)'},
54
- 'github-light': {bg:'#F6F8FA',surface:'#FFFFFF',surface2:'#EAEEF2',border:'#D0D7DE',border2:'#BCC3CB',text:'#1F2328',text2:'#656D76',text3:'#8C959F',accent:'#0969DA','accent-dim':'rgba(9,105,218,.1)',success:'#1A7F37','success-dim':'rgba(26,127,55,.1)',warning:'#9A6700','warning-dim':'rgba(154,103,0,.1)',danger:'#CF222E','danger-dim':'rgba(207,34,46,.1)',purple:'#8250DF','purple-dim':'rgba(130,80,223,.1)'},
55
- 'dracula': {bg:'#1E1F29',surface:'#282A36',surface2:'#2E303E',border:'#3A3C4E',border2:'#44475A',text:'#F8F8F2',text2:'#ADB0C0',text3:'#6272A4',accent:'#BD93F9','accent-dim':'rgba(189,147,249,.15)',success:'#50FA7B','success-dim':'rgba(80,250,123,.15)',warning:'#F1FA8C','warning-dim':'rgba(241,250,140,.15)',danger:'#FF5555','danger-dim':'rgba(255,85,85,.15)',purple:'#FF79C6','purple-dim':'rgba(255,121,198,.15)'},
56
- 'nord': {bg:'#242933',surface:'#2E3440',surface2:'#353C4A',border:'#3B4252',border2:'#434C5E',text:'#ECEFF4',text2:'#A5B1C2',text3:'#697688',accent:'#88C0D0','accent-dim':'rgba(136,192,208,.15)',success:'#A3BE8C','success-dim':'rgba(163,190,140,.15)',warning:'#EBCB8B','warning-dim':'rgba(235,203,139,.15)',danger:'#BF616A','danger-dim':'rgba(191,97,106,.15)',purple:'#B48EAD','purple-dim':'rgba(180,142,173,.15)'},
57
- 'solarized-dark': {bg:'#002129',surface:'#002B36',surface2:'#073642',border:'#0A4050',border2:'#1A5060',text:'#93A1A1',text2:'#839496',text3:'#586E75',accent:'#268BD2','accent-dim':'rgba(38,139,210,.15)',success:'#859900','success-dim':'rgba(133,153,0,.15)',warning:'#B58900','warning-dim':'rgba(181,137,0,.15)',danger:'#DC322F','danger-dim':'rgba(220,50,47,.15)',purple:'#6C71C4','purple-dim':'rgba(108,113,196,.15)'},
58
- 'solarized-light': {bg:'#FDF6E3',surface:'#FFFFFF',surface2:'#EEE8D5',border:'#DDD6C1',border2:'#C9C2AD',text:'#586E75',text2:'#657B83',text3:'#93A1A1',accent:'#268BD2','accent-dim':'rgba(38,139,210,.1)',success:'#859900','success-dim':'rgba(133,153,0,.1)',warning:'#B58900','warning-dim':'rgba(181,137,0,.1)',danger:'#DC322F','danger-dim':'rgba(220,50,47,.1)',purple:'#6C71C4','purple-dim':'rgba(108,113,196,.1)'},
59
- 'one-dark': {bg:'#1E2127',surface:'#282C34',surface2:'#2C313C',border:'#3A3F4B',border2:'#4B5263',text:'#ABB2BF',text2:'#7F848E',text3:'#5C6370',accent:'#61AFEF','accent-dim':'rgba(97,175,239,.15)',success:'#98C379','success-dim':'rgba(152,195,121,.15)',warning:'#E5C07B','warning-dim':'rgba(229,192,123,.15)',danger:'#E06C75','danger-dim':'rgba(224,108,117,.15)',purple:'#C678DD','purple-dim':'rgba(198,120,221,.15)'},
60
- 'gruvbox-dark': {bg:'#1D2021',surface:'#282828',surface2:'#32302F',border:'#3C3836',border2:'#504945',text:'#EBDBB2',text2:'#A89984',text3:'#7C6F64',accent:'#FE8019','accent-dim':'rgba(254,128,25,.15)',success:'#B8BB26','success-dim':'rgba(184,187,38,.15)',warning:'#FABD2F','warning-dim':'rgba(250,189,47,.15)',danger:'#FB4934','danger-dim':'rgba(251,73,52,.15)',purple:'#D3869B','purple-dim':'rgba(211,134,155,.15)'},
61
- };
62
-
63
- function generateThemeCss(themeId) {
64
- const vars = THEME_VARS[themeId];
65
- if (!vars) return '';
66
- const name = THEME_NAMES[themeId] || themeId;
67
- let css = '/* mdboard theme: ' + name + ' */\n:root {\n';
68
- for (const key in vars) {
69
- if (vars.hasOwnProperty(key)) {
70
- css += ' --' + key + ': ' + vars[key] + ';\n';
71
- }
72
- }
73
- css += '}\n';
74
- return css;
75
- }
76
-
77
- function handleTheme(args) {
78
- const themeName = args.filter(a => !a.startsWith('--'))[0];
79
- const isProject = args.includes('--project');
80
-
81
- if (!themeName) {
82
- listThemes();
83
- return;
84
- }
85
-
86
- if (!THEME_IDS.includes(themeName)) {
87
- console.error('\n Error: Unknown theme "' + themeName + '"');
88
- console.log(' Run "mdboard theme" to see available themes.\n');
89
- process.exit(1);
90
- }
91
-
92
- const configPath = isProject
93
- ? path.join(process.cwd(), 'project', 'mdboard.json')
94
- : path.join(os.homedir(), '.config', 'mdboard', 'mdboard.json');
95
-
96
- const dir = path.dirname(configPath);
97
- if (!fs.existsSync(dir)) {
98
- fs.mkdirSync(dir, { recursive: true });
99
- }
100
-
101
- let config = {};
102
- try {
103
- config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
104
- } catch {
105
- // file doesn't exist or is invalid, start fresh
106
- }
107
-
108
- config.theme = themeName;
109
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
110
-
111
- // Also write CSS file
112
- const css = generateThemeCss(themeName);
113
- const cssPath = isProject
114
- ? path.join(process.cwd(), 'project', 'mdboard.css')
115
- : path.join(os.homedir(), '.config', 'mdboard', 'mdboard.css');
116
- fs.writeFileSync(cssPath, css, 'utf-8');
117
-
118
- const scope = isProject ? 'project' : 'global';
119
- console.log('\n Theme set to "' + THEME_NAMES[themeName] + '" (' + scope + ')');
120
- console.log(' Config: ' + configPath);
121
- console.log(' CSS: ' + cssPath + '\n');
122
- }
123
-
124
- function listThemes() {
125
- const dark = THEME_IDS.filter(id => id.includes('dark') || ['catppuccin-mocha', 'tokyo-night', 'tokyo-night-storm', 'dracula', 'nord', 'one-dark', 'gruvbox-dark'].includes(id));
126
- const light = THEME_IDS.filter(id => !dark.includes(id));
127
-
128
- console.log('\n Available themes:\n');
129
- console.log(' Dark:');
130
- dark.forEach(function(id) {
131
- console.log(' ' + id.padEnd(24) + THEME_NAMES[id]);
132
- });
133
- console.log('\n Light:');
134
- light.forEach(function(id) {
135
- console.log(' ' + id.padEnd(24) + THEME_NAMES[id]);
136
- });
137
- console.log('\n Usage:');
138
- console.log(' mdboard theme <name> Set theme globally');
139
- console.log(' mdboard theme <name> --project Set theme for current project\n');
140
- }
141
-
142
- module.exports = { handleTheme };
package/src/client/app.js DELETED
@@ -1,266 +0,0 @@
1
- /* ══════════════════════════════════════════════════════════════
2
- RENDER
3
- ══════════════════════════════════════════════════════════════ */
4
- function renderAll() {
5
- renderHeader(); renderBoardFilters(); renderBoard(); renderTableControls(); renderTableBody(); renderMsFilters(); renderMilestones(); renderMetrics();
6
- // Render overview if active
7
- var overviewView = document.getElementById('view-overview');
8
- if (overviewView && overviewView.classList.contains('active')) {
9
- renderOverview();
10
- }
11
- }
12
-
13
- function renderHeader() {
14
- renderSidebarLogo();
15
-
16
- var mw = document.getElementById('h-milestone-wrap');
17
- var am = D.milestones.find(function(m) { return m.status === 'active'; });
18
- if (am) {
19
- mw.style.display = '';
20
- document.getElementById('h-milestone-name').textContent = am.title || am.id || '';
21
- document.getElementById('h-milestone-progress').style.width = (am.progress || 0) + '%';
22
- } else { mw.style.display = 'none'; }
23
-
24
- var sw = document.getElementById('h-sprint-wrap');
25
- var as = D.sprints.find(function(s) { return s.status === 'active'; });
26
- if (as) {
27
- sw.style.display = '';
28
- document.getElementById('h-sprint-name').textContent = as.goal || as.id || '';
29
- var dr = daysRemaining(as.end_date);
30
- document.getElementById('h-sprint-days').textContent = dr !== null ? (dr >= 0 ? dr + ' days left' : Math.abs(dr) + ' days over') : '';
31
- } else { sw.style.display = 'none'; }
32
-
33
- var taskPlural = ENTITY_NAMES.task ? ENTITY_NAMES.task.plural : 'Tasks';
34
- var total = D.tasks.length;
35
- var done = D.tasks.filter(function(f) { return f.status === COMPLETED_STATUS; }).length;
36
- var inProgStatus = (D.config && D.config.statuses && D.config.statuses.task) ?
37
- ((D.config.statuses.task.find(function(s) { return s.icon === 'half-circle'; }) || {}).key || 'in-progress') : 'in-progress';
38
- var inProg = D.tasks.filter(function(f) { return f.status === inProgStatus; }).length;
39
- var vel = D.health && D.health.velocity != null ? D.health.velocity : (total ? Math.round(done / total * 100) : 0);
40
- document.getElementById('h-stats').innerHTML =
41
- '<div class="stat"><span class="stat-val">' + total + '</span><span class="stat-label">' + escHtml(taskPlural) + '</span></div>' +
42
- '<div class="stat"><span class="stat-val" style="color:var(--success)">' + done + '</span><span class="stat-label">Done</span></div>' +
43
- '<div class="stat"><span class="stat-val" style="color:var(--warning)">' + inProg + '</span><span class="stat-label">In Progress</span></div>' +
44
- '<div class="stat"><span class="stat-val" style="color:var(--accent)">' + vel + '%</span><span class="stat-label">Velocity</span></div>';
45
- }
46
-
47
- /* ══════════════════════════════════════════════════════════════
48
- NAVIGATION
49
- ══════════════════════════════════════════════════════════════ */
50
- function switchView(name) {
51
- document.querySelectorAll('.view').forEach(function(v) { v.classList.remove('active'); });
52
- document.querySelectorAll('#sidebar-nav a[data-view]').forEach(function(a) { a.classList.remove('active'); });
53
- var t = document.getElementById('view-' + name);
54
- if (t) t.classList.add('active');
55
- var l = document.querySelector('#sidebar-nav a[data-view="' + name + '"]');
56
- if (l) l.classList.add('active');
57
- // Persist view selection
58
- try { localStorage.setItem('mdboard-view', name); } catch(e) {}
59
- // Trigger overview rendering when switching to it
60
- if (name === 'overview' && D.loaded) {
61
- renderOverview();
62
- }
63
- // Trigger notes rendering when switching to it
64
- if (name === 'notes' && D.loaded) {
65
- renderNotes();
66
- }
67
- }
68
-
69
- document.getElementById('sidebar-nav').addEventListener('click', function(e) {
70
- var a = e.target.closest('a[data-view]');
71
- if (!a) return;
72
- e.preventDefault();
73
- switchView(a.dataset.view);
74
- window.location.hash = a.dataset.view;
75
- });
76
-
77
- function handleHash() {
78
- var hash = window.location.hash.replace('#', '');
79
- if (!hash) {
80
- try { hash = localStorage.getItem('mdboard-view') || ''; } catch(e) {}
81
- }
82
- hash = hash || 'board';
83
- var valid = ['board','table','milestones','metrics','overview','notes'];
84
- switchView(valid.indexOf(hash) !== -1 ? hash : 'board');
85
- }
86
- window.addEventListener('hashchange', handleHash);
87
-
88
- /* ══════════════════════════════════════════════════════════════
89
- SETTINGS PANEL
90
- ══════════════════════════════════════════════════════════════ */
91
- var _settingsState = { selectedTheme: null, originalTheme: null };
92
-
93
- function openSettings() {
94
- var active = getActiveTheme();
95
- _settingsState.originalTheme = active;
96
- _settingsState.selectedTheme = null;
97
- document.getElementById('settings-overlay').classList.add('open');
98
- document.getElementById('settings-panel').classList.add('open');
99
- var toggle = document.getElementById('theme-default-toggle');
100
- if (toggle) toggle.checked = false;
101
- renderThemeGrid();
102
- }
103
-
104
- function closeSettings() {
105
- if (_settingsState.selectedTheme) {
106
- revertThemePreview();
107
- _settingsState.selectedTheme = null;
108
- }
109
- document.getElementById('settings-overlay').classList.remove('open');
110
- document.getElementById('settings-panel').classList.remove('open');
111
- }
112
-
113
- function renderThemeGrid() {
114
- var grid = document.getElementById('theme-grid');
115
- var info = document.getElementById('settings-theme-info');
116
- var active = getActiveTheme();
117
- var selected = _settingsState.selectedTheme;
118
-
119
- if (info) {
120
- var displayId = selected || active;
121
- var displayTheme = THEMES[displayId];
122
- info.textContent = 'Active: ' + (THEMES[active] ? THEMES[active].name : active) +
123
- (selected && selected !== active ? ' → Preview: ' + (displayTheme ? displayTheme.name : selected) : '');
124
- }
125
-
126
- var html = '';
127
- THEME_LIST.forEach(function(t) {
128
- var theme = THEMES[t.id];
129
- var v = theme.vars;
130
- var isSelected = selected ? t.id === selected : t.id === active;
131
- var cls = isSelected ? ' active' : '';
132
- html += '<div class="theme-card' + cls + '" data-theme="' + t.id + '">' +
133
- '<div class="theme-card-preview">' +
134
- '<span style="background:' + v.bg + '"></span>' +
135
- '<span style="background:' + v.surface + '"></span>' +
136
- '<span style="background:' + v.accent + '"></span>' +
137
- '<span style="background:' + v.success + '"></span>' +
138
- '<span style="background:' + v.warning + '"></span>' +
139
- '</div>' +
140
- '<div class="theme-card-name">' + escHtml(theme.name) + '</div>' +
141
- '<div class="theme-card-type">' + theme.type + '</div>' +
142
- '</div>';
143
- });
144
- grid.innerHTML = html;
145
- }
146
-
147
- function saveTheme() {
148
- var themeId = _settingsState.selectedTheme;
149
- if (!themeId) {
150
- closeSettings();
151
- return;
152
- }
153
- var toggle = document.getElementById('theme-default-toggle');
154
- var setDefault = toggle ? toggle.checked : false;
155
- var css = generateThemeCss(themeId);
156
-
157
- fetch('/api/theme', {
158
- method: 'POST',
159
- headers: { 'Content-Type': 'application/json' },
160
- body: JSON.stringify({ themeId: themeId, css: css, setDefault: setDefault })
161
- }).then(function(r) { return r.json(); }).then(function(data) {
162
- if (data && data.ok) {
163
- revertThemePreview();
164
- reloadCustomCss();
165
- D.config.theme = themeId;
166
- _settingsState.selectedTheme = null;
167
- showToast('Theme saved: ' + (THEMES[themeId] ? THEMES[themeId].name : themeId), 'success');
168
- closeSettings();
169
- } else {
170
- showToast('Error: ' + (data.error || 'Unknown'), 'error');
171
- }
172
- }).catch(function(e) {
173
- showToast('Error: ' + e.message, 'error');
174
- });
175
- }
176
-
177
- document.getElementById('settings-toggle').addEventListener('click', function(e) {
178
- e.preventDefault();
179
- openSettings();
180
- });
181
-
182
- document.getElementById('settings-close').addEventListener('click', closeSettings);
183
- document.getElementById('settings-overlay').addEventListener('click', closeSettings);
184
-
185
- document.getElementById('theme-grid').addEventListener('click', function(e) {
186
- var card = e.target.closest('.theme-card');
187
- if (!card) return;
188
- var themeId = card.dataset.theme;
189
- _settingsState.selectedTheme = themeId;
190
- applyTheme(themeId);
191
- renderThemeGrid();
192
- });
193
-
194
- document.getElementById('settings-save').addEventListener('click', saveTheme);
195
-
196
- /* ══════════════════════════════════════════════════════════════
197
- INIT
198
- ══════════════════════════════════════════════════════════════ */
199
- (async function() {
200
- handleHash();
201
- await loadAll();
202
- initFromConfig();
203
-
204
- // Sidebar logo always shows project name
205
- renderSidebarLogo();
206
-
207
- // Workspace detection: build source rail + restore last source
208
- if (hasWorkspace()) {
209
- // Create overview tab (hidden by default)
210
- var overviewTab = document.querySelector('#sidebar-nav a[data-view="overview"]');
211
- if (!overviewTab) {
212
- var nav = document.getElementById('sidebar-nav');
213
- var metricsLink = document.querySelector('#sidebar-nav a[data-view="metrics"]');
214
- var overviewLink = document.createElement('a');
215
- overviewLink.href = '#overview';
216
- overviewLink.dataset.view = 'overview';
217
- overviewLink.style.display = 'none';
218
- overviewLink.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="9"/><path d="M12 3v18M3 12h18"/></svg><span>Overview</span>';
219
- if (metricsLink && metricsLink.nextSibling) {
220
- nav.insertBefore(overviewLink, metricsLink.nextSibling);
221
- } else {
222
- nav.appendChild(overviewLink);
223
- }
224
- }
225
-
226
- // Restore last source or default to overview
227
- var savedSource = null;
228
- try { savedSource = localStorage.getItem('mdboard-source'); } catch(e) {}
229
- var validSource = savedSource && (savedSource === 'overview' || D.config.workspace.sources.some(function(s) { return s.name === savedSource; }));
230
- var initialSource = validSource ? savedSource : 'overview';
231
-
232
- buildSourceRail();
233
- // switchSource skips if same as current, so set activeSource first then trigger
234
- D.activeSource = null;
235
- switchSource(initialSource);
236
- // Wait for switchSource data reload before continuing
237
- await loadAll();
238
- initFromConfig();
239
- }
240
-
241
- renderAll();
242
- connectSSE();
243
-
244
- // Auto-open history modal if no project
245
- checkAutoOpenHistory();
246
-
247
- // Keyboard shortcut: Ctrl/Cmd+K to open history modal
248
- document.addEventListener('keydown', function(e) {
249
- if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
250
- e.preventDefault();
251
- openHistoryModal();
252
- }
253
- });
254
-
255
- // Load AI suggestions for autocomplete
256
- fetchJson('/api/ai-suggestions').then(function(data) {
257
- D.aiSuggestions = data || { skills: [], agents: [], mcps: [], commands: [], context: [] };
258
- });
259
-
260
- // Load overview links in background for reverse link display
261
- if (hasWorkspace()) {
262
- fetchJson('/api/overview/links').then(function(data) {
263
- D.overviewLinks = data;
264
- });
265
- }
266
- })();
@@ -1,157 +0,0 @@
1
- /* ══════════════════════════════════════════════════════════════
2
- BOARD VIEW
3
- ══════════════════════════════════════════════════════════════ */
4
- var boardFilters = { search: '', priority: '', epic: '', milestone: '' };
5
-
6
- function renderBoardFilters() {
7
- var c = document.getElementById('board-filters');
8
- var ep = [], ms = [];
9
- D.tasks.forEach(function(f) {
10
- if (f.epic && ep.indexOf(f.epic) === -1) ep.push(f.epic);
11
- if (f.milestone && ms.indexOf(f.milestone) === -1) ms.push(f.milestone);
12
- });
13
-
14
- var epicPlural = ENTITY_NAMES.epic ? ENTITY_NAMES.epic.plural : 'Epics';
15
- var msPlural = ENTITY_NAMES.milestone ? ENTITY_NAMES.milestone.plural : 'Milestones';
16
-
17
- function opts(arr, key, label) {
18
- return '<select data-filter="' + key + '"><option value="">All ' + label + '</option>' +
19
- arr.map(function(v) { return '<option value="' + escHtml(v) + '"' + (boardFilters[key] === v ? ' selected' : '') + '>' + escHtml(v) + '</option>'; }).join('') + '</select>';
20
- }
21
-
22
- var createBtn = isSourceWritable() ?
23
- '<button class="btn btn-sm btn-create" id="board-create-btn">+ New ' + escHtml(ENTITY_NAMES.task ? ENTITY_NAMES.task.singular : 'Task') + '</button>' : '';
24
-
25
- c.innerHTML = createBtn +
26
- '<input type="text" data-filter="search" placeholder="Search cards..." value="' + escHtml(boardFilters.search) + '">' +
27
- '<select data-filter="priority"><option value="">All Priorities</option>' +
28
- PRIORITIES.map(function(p) { return '<option value="' + p + '"' + (boardFilters.priority === p ? ' selected' : '') + '>' + (PRIORITY_LABELS[p] || p) + '</option>'; }).join('') + '</select>' +
29
- opts(ep, 'epic', epicPlural) + opts(ms, 'milestone', msPlural);
30
-
31
- c.querySelectorAll('input[data-filter]').forEach(function(el) {
32
- el.addEventListener('input', function() { boardFilters[el.dataset.filter] = el.value; renderBoard(); });
33
- });
34
- c.querySelectorAll('select[data-filter]').forEach(function(el) {
35
- el.addEventListener('change', function() { boardFilters[el.dataset.filter] = el.value; renderBoard(); });
36
- });
37
-
38
- var btn = document.getElementById('board-create-btn');
39
- if (btn) btn.addEventListener('click', function() { openCreateDialog('tasks'); });
40
- }
41
-
42
- function getFilteredBoardTasks() {
43
- var list = D.tasks.slice();
44
- var f = boardFilters;
45
- if (f.search) { var q = f.search.toLowerCase(); list = list.filter(function(x) { return (x.id || '').toLowerCase().indexOf(q) !== -1 || (x.title || '').toLowerCase().indexOf(q) !== -1; }); }
46
- if (f.priority) list = list.filter(function(x) { return x.priority === f.priority; });
47
- if (f.epic) list = list.filter(function(x) { return x.epic === f.epic; });
48
- if (f.milestone) list = list.filter(function(x) { return x.milestone === f.milestone; });
49
- return list;
50
- }
51
-
52
- function renderBoard() {
53
- var sprint = D.sprints.find(function(s) { return s.status === 'active'; });
54
- var sbar = document.getElementById('sprint-bar');
55
- if (sprint) {
56
- var planned = sprint.planned_points || 0;
57
- var completed = sprint.completed_points || 0;
58
- var pct = planned ? Math.round(completed / planned * 100) : 0;
59
- sbar.style.display = '';
60
- sbar.innerHTML =
61
- '<div class="sprint-goal"><b>' + escHtml(sprint.id || '') + '</b>' + (sprint.goal ? ' &mdash; ' + escHtml(sprint.goal) : '') + '</div>' +
62
- '<div style="width:160px"><div class="progress progress-accent"><div class="progress-fill" style="width:' + pct + '%"></div></div>' +
63
- '<div style="font-size:11px;color:var(--text3);margin-top:2px;font-family:var(--mono)">' + completed + '/' + planned + ' pts</div></div>' +
64
- '<div class="sprint-dates">' + fmtDate(sprint.start_date) + ' &mdash; ' + fmtDate(sprint.end_date) + '</div>';
65
- } else { sbar.style.display = 'none'; }
66
-
67
- var board = document.getElementById('board');
68
- if (!D.loaded) {
69
- board.innerHTML = BOARD_COLS.map(function() {
70
- return '<div class="column"><div class="column-header"><div class="skeleton skeleton-line" style="width:80px;height:16px"></div></div><div class="column-body">' +
71
- '<div class="skeleton skeleton-card"></div><div class="skeleton skeleton-card"></div></div></div>';
72
- }).join('');
73
- return;
74
- }
75
-
76
- var filteredTasks = getFilteredBoardTasks();
77
- var taskSingular = ENTITY_NAMES.task ? ENTITY_NAMES.task.singular.toLowerCase() : 'task';
78
-
79
- if (!D.tasks.length) {
80
- board.innerHTML = '<div class="empty"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="3"/><path d="M9 12h6M12 9v6"/></svg><p>No ' + escHtml(taskSingular) + 's yet. Create ' + escHtml(taskSingular) + ' files in your project/ directory to get started.</p></div>';
81
- return;
82
- }
83
-
84
- board.innerHTML = BOARD_COLS.map(function(status) {
85
- var cards = filteredTasks.filter(function(f) { return f.status === status; });
86
- return '<div class="column" data-status="' + status + '">' +
87
- '<div class="column-header"><span class="col-title">' + statusIcon(status) + ' ' + (STATUS_LABELS[status] || status) + '</span><span class="col-count">' + cards.length + '</span></div>' +
88
- '<div class="column-body">' + cards.map(renderCard).join('') + '</div></div>';
89
- }).join('');
90
-
91
- setupDragDrop();
92
- }
93
-
94
- function renderCard(f) {
95
- var ec = f.epic ? epicColor(f.epic) : '';
96
- var assigned = Array.isArray(f.assigned) ? f.assigned.join(', ') : (f.assigned || '');
97
- return '<div class="card" draggable="true" data-id="' + escHtml(f.id || '') + '" data-status="' + escHtml(f.status || '') + '">' +
98
- '<div class="card-header">' +
99
- '<span class="card-priority">' + priorityIcon(f.priority) + '</span>' +
100
- '<span class="card-id">' + escHtml(f.id || '') + '</span>' +
101
- '</div>' +
102
- '<div class="card-title">' + escHtml(f.title || '') + '</div>' +
103
- '<div class="card-meta">' +
104
- (f.points != null ? '<span class="pill pill-points">' + f.points + ' pts</span>' : '') +
105
- (f.epic ? '<span class="pill pill-epic" style="background:' + ec + '">' + escHtml(f.epic) + '</span>' : '') +
106
- (f.ai && Object.keys(f.ai).length > 0 ? '<span class="pill pill-ai" title="AI properties">&#9889;</span>' : '') +
107
- (assigned ? '<span class="card-assigned">' + escHtml(assigned) + '</span>' : '') +
108
- '</div></div>';
109
- }
110
-
111
- /* ── Drag & Drop ─────────────────────────────────────────── */
112
- function setupDragDrop() {
113
- document.querySelectorAll('.card[draggable]').forEach(function(card) {
114
- card.addEventListener('dragstart', function(e) {
115
- e.dataTransfer.setData('text/plain', card.dataset.id);
116
- e.dataTransfer.effectAllowed = 'move';
117
- card.classList.add('dragging');
118
- });
119
- card.addEventListener('dragend', function() {
120
- card.classList.remove('dragging');
121
- document.querySelectorAll('.column.drag-over').forEach(function(c) { c.classList.remove('drag-over'); });
122
- });
123
- card.addEventListener('click', function(e) {
124
- if (e.defaultPrevented) return;
125
- var task = D.tasks.find(function(f) { return f.id === card.dataset.id; });
126
- if (task) openPanel('tasks', task);
127
- });
128
- });
129
-
130
- document.querySelectorAll('.column').forEach(function(col) {
131
- col.addEventListener('dragover', function(e) {
132
- e.preventDefault();
133
- e.dataTransfer.dropEffect = 'move';
134
- col.classList.add('drag-over');
135
- });
136
- col.addEventListener('dragleave', function(e) {
137
- if (e.target === col || !col.contains(e.relatedTarget)) {
138
- col.classList.remove('drag-over');
139
- }
140
- });
141
- col.addEventListener('drop', function(e) {
142
- e.preventDefault();
143
- col.classList.remove('drag-over');
144
- var taskId = e.dataTransfer.getData('text/plain');
145
- var newStatus = col.dataset.status;
146
- if (!taskId || !newStatus) return;
147
-
148
- // Optimistic update
149
- var task = D.tasks.find(function(f) { return f.id === taskId; });
150
- if (task && task.status !== newStatus) {
151
- task.status = newStatus;
152
- renderBoard();
153
- patchItem('tasks', taskId, { status: newStatus });
154
- }
155
- });
156
- });
157
- }