mdboard 1.2.0 → 1.3.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.
package/index.html CHANGED
@@ -71,6 +71,8 @@ a{color:var(--accent);text-decoration:none}
71
71
  .content{flex:1;overflow-y:auto;padding:20px 24px}
72
72
  .view{display:none}
73
73
  .view.active{display:block}
74
+ #view-notes.active{display:flex;flex-direction:column;margin:-20px -24px;height:calc(100% + 40px)}
75
+ #view-notes #notes-container{flex:1;overflow:hidden}
74
76
 
75
77
  /* ── Icons ───────────────────────────────────────────────── */
76
78
  .icon{width:16px;height:16px;flex-shrink:0;vertical-align:middle;display:inline-block}
@@ -174,26 +176,60 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
174
176
  .health-label{flex:1;font-size:13px;color:var(--text2)}
175
177
  .health-val{font-family:var(--mono);font-size:13px;font-weight:600}
176
178
 
177
- /* ── Detail Panel ────────────────────────────────────────── */
179
+ /* ── Detail Panel Notion-style ─────────────────────────── */
178
180
  .panel-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:99;opacity:0;pointer-events:none;transition:opacity .2s}
179
181
  .panel-overlay.open{opacity:1;pointer-events:auto}
180
- .detail-panel{position:fixed;top:0;right:0;width:520px;max-width:92vw;height:100vh;background:var(--surface);border-left:1px solid var(--border);z-index:100;transform:translateX(100%);transition:transform .25s ease;display:flex;flex-direction:column;overflow:hidden}
182
+ .detail-panel{position:fixed;top:0;right:0;width:600px;max-width:92vw;height:100vh;background:var(--bg);border-left:1px solid var(--border);z-index:100;transform:translateX(100%);transition:transform .25s ease;display:flex;flex-direction:column;overflow:hidden}
181
183
  .detail-panel.open{transform:translateX(0)}
182
- .panel-header{padding:16px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;flex-shrink:0}
184
+ .panel-header{padding:12px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;flex-shrink:0;background:var(--surface)}
183
185
  .panel-type{font-size:10px;padding:3px 8px;border-radius:var(--radius-sm);background:var(--accent-dim);color:var(--accent);font-weight:700;text-transform:uppercase;letter-spacing:.04em}
184
186
  .panel-item-id{font-family:var(--mono);font-size:13px;color:var(--text2)}
185
187
  .panel-close{margin-left:auto;background:none;border:none;color:var(--text2);cursor:pointer;font-size:18px;padding:4px 8px;border-radius:var(--radius-sm);line-height:1}
186
188
  .panel-close:hover{background:var(--surface2);color:var(--text)}
187
- .panel-body{flex:1;overflow-y:auto;padding:20px}
188
- .panel-field{margin-bottom:16px}
189
- .panel-field label{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--text3);font-weight:600;margin-bottom:6px}
190
- .panel-field input,.panel-field select,.panel-field textarea{width:100%;background:var(--bg);border:1px solid var(--border);color:var(--text);padding:8px 12px;border-radius:var(--radius-sm);font-family:var(--font);font-size:13px}
191
- .panel-field input:focus,.panel-field select:focus,.panel-field textarea:focus{outline:none;border-color:var(--accent)}
192
- .panel-field textarea{min-height:120px;resize:vertical;font-family:var(--mono);font-size:12px;line-height:1.6}
193
- .panel-field .field-with-icon{display:flex;align-items:center;gap:8px}
194
- .panel-field .field-with-icon select{flex:1}
195
- .panel-props{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:16px}
196
- .panel-footer{padding:16px 20px;border-top:1px solid var(--border);display:flex;gap:8px;justify-content:flex-end;flex-shrink:0}
189
+
190
+ /* Panel title — large Notion-style */
191
+ .panel-title-wrap{padding:20px 24px 8px;flex-shrink:0}
192
+ .panel-title-input{width:100%;font-size:28px;font-weight:700;background:transparent;border:none;color:var(--text);font-family:var(--font);outline:none;padding:0;line-height:1.3}
193
+ .panel-title-input::placeholder{color:var(--text3)}
194
+ .panel-title-input:disabled{opacity:.7}
195
+
196
+ /* Panel properties — compact Notion-style rows */
197
+ .panel-properties{padding:4px 24px 8px;flex-shrink:0;border-bottom:1px solid var(--border)}
198
+ .prop-row{display:flex;align-items:center;padding:4px 0;min-height:32px}
199
+ .prop-label{width:100px;flex-shrink:0;font-size:12px;color:var(--text3);font-weight:500}
200
+ .prop-value{flex:1;display:flex;align-items:center;gap:6px}
201
+ .prop-value select,.prop-value input[type="text"],.prop-value input[type="number"],.prop-value input[type="date"]{background:transparent;border:1px solid transparent;color:var(--text);padding:4px 8px;border-radius:var(--radius-sm);font-family:var(--font);font-size:13px;width:100%}
202
+ .prop-value select:hover,.prop-value input:hover{border-color:var(--border)}
203
+ .prop-value select:focus,.prop-value input:focus{outline:none;border-color:var(--accent);background:var(--surface)}
204
+ .prop-value select:disabled,.prop-value input:disabled{opacity:.6;cursor:default}
205
+ .prop-value select:disabled:hover,.prop-value input:disabled:hover{border-color:transparent}
206
+ .prop-text{font-size:13px;color:var(--text2);padding:4px 8px}
207
+
208
+ /* AI Tag Input */
209
+ .prop-row-divider{margin-top:4px;border-top:1px solid var(--border);padding-top:8px}
210
+ .prop-row-toggle{cursor:pointer;user-select:none}
211
+ .prop-row-toggle:hover{background:var(--surface2);border-radius:var(--radius-sm)}
212
+ .ai-toggle-arrow{display:inline-block;font-size:10px;width:14px;transition:transform .15s}
213
+ .ai-tag-input{flex:1;display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:2px 4px;min-height:28px;border:1px solid transparent;border-radius:var(--radius-sm);cursor:text;position:relative;transition:border-color .15s}
214
+ .ai-tag-input:hover{border-color:var(--border)}
215
+ .ai-tag-input:focus-within{border-color:var(--accent);background:var(--surface)}
216
+ .ai-tag{display:inline-flex;align-items:center;gap:2px;padding:1px 8px;border-radius:10px;font-size:11px;font-weight:500;white-space:nowrap;background:var(--surface2);color:var(--text2);border:1px solid var(--border)}
217
+ .ai-tag-own{background:var(--accent-dim);color:var(--accent);border-color:var(--accent)}
218
+ .ai-tag-inherited{border-style:dashed;opacity:.6}
219
+ .ai-tag-remove{background:none;border:none;color:inherit;cursor:pointer;font-size:13px;line-height:1;padding:0 0 0 2px;opacity:.6}
220
+ .ai-tag-remove:hover{opacity:1}
221
+ .ai-tag-text{border:none!important;background:transparent!important;padding:2px 4px!important;font-size:12px;min-width:60px;flex:1;outline:none;color:var(--text)}
222
+ .ai-tag-text::placeholder{color:var(--text3);font-size:11px}
223
+ .ai-autocomplete{position:absolute;top:100%;left:0;right:0;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:0 8px 24px rgba(0,0,0,.4);z-index:50;max-height:160px;overflow-y:auto;margin-top:2px;display:none}
224
+ .ai-autocomplete.open{display:block}
225
+ .ai-autocomplete-item{padding:6px 10px;font-size:12px;color:var(--text2);cursor:pointer;transition:background .1s}
226
+ .ai-autocomplete-item:hover,.ai-autocomplete-item.active{background:var(--accent-dim);color:var(--text)}
227
+
228
+ /* Panel content area — fills remaining space */
229
+ .panel-content-area{flex:1;overflow-y:auto;padding:0}
230
+ .panel-editor-zone{padding:16px 24px;min-height:200px}
231
+
232
+ .panel-footer{padding:12px 20px;border-top:1px solid var(--border);display:flex;gap:8px;align-items:center;flex-shrink:0}
197
233
  .btn{padding:8px 16px;border-radius:var(--radius-sm);font-size:13px;font-weight:500;cursor:pointer;border:1px solid var(--border);background:var(--surface2);color:var(--text);transition:all .15s;font-family:var(--font)}
198
234
  .btn:hover{background:var(--border)}
199
235
  .btn-primary{background:var(--accent);border-color:var(--accent);color:#fff}
@@ -243,6 +279,178 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
243
279
  .tracked-ms-item span{font-size:12px}
244
280
  .tracked-ms-pct{font-family:var(--mono);font-weight:600;font-size:12px;min-width:36px;text-align:right}
245
281
 
282
+ /* ── Notes View — Notion-style ───────────────────────────── */
283
+ .notes-layout{display:flex;height:100%;gap:0}
284
+ .notes-sidebar{width:260px;flex-shrink:0;border-right:1px solid var(--border);display:flex;flex-direction:column;background:var(--surface)}
285
+ .notes-toolbar{padding:10px 12px;display:flex;gap:8px;border-bottom:1px solid var(--border)}
286
+ .notes-toolbar input{flex:1;background:var(--bg);border:1px solid var(--border);color:var(--text);padding:6px 10px;border-radius:var(--radius-sm);font-size:12px;font-family:var(--font)}
287
+ .notes-toolbar input:focus{outline:none;border-color:var(--accent)}
288
+ .notes-list{flex:1;overflow-y:auto}
289
+ .notes-list-item{padding:10px 16px;border-bottom:1px solid var(--border);cursor:pointer;transition:background .15s}
290
+ .notes-list-item:hover{background:var(--surface2)}
291
+ .notes-list-item.active{background:var(--accent-dim);border-left:3px solid var(--accent)}
292
+ .notes-list-title{font-size:13px;font-weight:600;margin-bottom:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
293
+ .notes-list-date{font-size:11px;color:var(--text3);font-family:var(--mono)}
294
+ .notes-list-empty{padding:40px 20px;text-align:center;color:var(--text3);font-size:13px}
295
+ .notes-editor{flex:1;display:flex;flex-direction:column;overflow:hidden;background:var(--bg)}
296
+ .notes-header-bar{padding:32px 48px 0;flex-shrink:0}
297
+ .notes-title-input{width:100%;font-size:32px;font-weight:700;background:transparent;border:none;color:var(--text);font-family:var(--font);outline:none;padding:0;margin-bottom:4px;line-height:1.3}
298
+ .notes-title-input::placeholder{color:var(--text3)}
299
+ .notes-meta{font-size:11px;color:var(--text3);font-family:var(--mono)}
300
+ .notes-editorjs-wrap{flex:1;overflow-y:auto;padding:16px 40px 40px}
301
+ .notes-editor-footer{padding:10px 24px;border-top:1px solid var(--border);display:flex;align-items:center;gap:8px;flex-shrink:0;background:var(--surface)}
302
+ .notes-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--text3);font-size:14px}
303
+ .notes-empty-state kbd{display:inline-block;padding:2px 6px;background:var(--surface2);border:1px solid var(--border);border-radius:3px;font-family:var(--mono);font-size:12px}
304
+ .notes-editor-loading{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text3)}
305
+
306
+ /* ── Editor.js — Dark theme overrides ────────────────────── */
307
+ .codex-editor{color:var(--text);font-family:var(--font)}
308
+ .codex-editor .ce-block__content{max-width:100%;margin:0}
309
+ .codex-editor .ce-toolbar__content{max-width:100%}
310
+ .codex-editor .ce-toolbar__plus{color:var(--text3);background:transparent;border:1px solid var(--border);width:26px;height:26px}
311
+ .codex-editor .ce-toolbar__plus:hover{background:var(--surface2);color:var(--text)}
312
+ .codex-editor .ce-toolbar__plus svg{color:inherit}
313
+ .codex-editor .ce-toolbar__settings-btn{color:var(--text3);background:transparent;width:26px;height:26px}
314
+ .codex-editor .ce-toolbar__settings-btn:hover{background:var(--surface2);color:var(--text)}
315
+ .codex-editor .ce-toolbar__actions{background:transparent}
316
+ /* Inline toolbar */
317
+ .codex-editor .ce-inline-toolbar{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:0 8px 24px rgba(0,0,0,.4)}
318
+ .codex-editor .ce-inline-toolbar__buttons{color:var(--text)}
319
+ .codex-editor .ce-inline-tool{color:var(--text2)}
320
+ .codex-editor .ce-inline-tool:hover{color:var(--text);background:var(--surface2)}
321
+ .codex-editor .ce-inline-tool--active{color:var(--accent)}
322
+ .codex-editor .ce-inline-tool-input{background:var(--bg);color:var(--text);border:none;border-top:1px solid var(--border)}
323
+ /* Conversion toolbar */
324
+ .codex-editor .ce-conversion-toolbar{background:var(--surface);border:1px solid var(--border);box-shadow:0 8px 24px rgba(0,0,0,.4)}
325
+ .codex-editor .ce-conversion-tool{color:var(--text2)}
326
+ .codex-editor .ce-conversion-tool:hover{background:var(--surface2);color:var(--text)}
327
+ .codex-editor .ce-conversion-tool--active{color:var(--accent)}
328
+ .codex-editor .ce-conversion-tool__icon{background:var(--surface2);color:var(--text2)}
329
+ /* Settings panel */
330
+ .codex-editor .ce-settings{background:var(--surface);border:1px solid var(--border);box-shadow:0 8px 24px rgba(0,0,0,.4)}
331
+ .codex-editor .cdx-settings-button{color:var(--text2)}
332
+ .codex-editor .cdx-settings-button:hover{background:var(--surface2);color:var(--text)}
333
+ .codex-editor .cdx-settings-button--active{color:var(--accent)}
334
+ /* Popover (slash commands menu) */
335
+ .ce-popover{background:var(--surface)!important;border:1px solid var(--border)!important;box-shadow:0 8px 24px rgba(0,0,0,.4)!important;color:var(--text)}
336
+ .ce-popover__container{background:var(--surface)!important;border:1px solid var(--border)!important;box-shadow:0 8px 24px rgba(0,0,0,.4)!important}
337
+ .ce-popover-item{color:var(--text2)!important}
338
+ .ce-popover-item:hover{background:var(--surface2)!important;color:var(--text)!important}
339
+ .ce-popover-item--focused{background:var(--surface2)!important;color:var(--text)!important}
340
+ .ce-popover-item__icon{background:var(--surface2)!important;color:var(--text2)!important;border:none!important}
341
+ .ce-popover-item:hover .ce-popover-item__icon{color:var(--text)!important}
342
+ .ce-popover-item__title{color:var(--text)!important}
343
+ .ce-popover-item__secondary-title{color:var(--text3)!important}
344
+ .ce-popover__search{background:transparent!important;border-bottom:1px solid var(--border)!important}
345
+ .ce-popover__search input,.cdx-search-field__input{color:var(--text)!important;background:var(--bg)!important;border:1px solid var(--border)!important;border-radius:var(--radius-sm)!important}
346
+ .cdx-search-field{background:transparent!important}
347
+ .ce-popover__nothing-found-message{color:var(--text3)!important}
348
+ .ce-popover__items::-webkit-scrollbar{width:4px}
349
+ .ce-popover__items::-webkit-scrollbar-track{background:transparent}
350
+ .ce-popover__items::-webkit-scrollbar-thumb{background:var(--border2);border-radius:2px}
351
+ .ce-block{padding:3px 0}
352
+ .ce-paragraph{line-height:1.65;font-size:15px}
353
+ .ce-paragraph[data-placeholder]:empty::before{color:var(--text3)!important}
354
+ .ce-header{font-weight:700}
355
+ h1.ce-header{font-size:28px;line-height:1.2;margin:8px 0 2px}
356
+ h2.ce-header{font-size:22px;line-height:1.25;margin:6px 0 2px}
357
+ h3.ce-header{font-size:18px;line-height:1.3;margin:4px 0 2px}
358
+ h4.ce-header{font-size:15px;line-height:1.4}
359
+ .ce-code__textarea{background:var(--surface)!important;color:var(--text)!important;border:none!important;border-radius:var(--radius-sm)!important;font-family:var(--mono)!important;font-size:13px!important;padding:16px!important;line-height:1.5!important}
360
+ .cdx-list{font-size:15px;line-height:1.65}
361
+ .cdx-list__item{color:var(--text)}
362
+ /* Checklist in list v2 */
363
+ .cdx-checklist{font-size:15px}
364
+ .cdx-checklist__item-checkbox{background:var(--surface2);border:2px solid var(--border2);border-radius:3px}
365
+ .cdx-checklist__item--checked .cdx-checklist__item-checkbox{background:var(--accent);border-color:var(--accent)}
366
+ .cdx-checklist__item--checked .cdx-checklist__item-text{color:var(--text2);text-decoration:line-through}
367
+ /* List v2 checklist style */
368
+ .cdx-list--checklist .cdx-list__item-checkbox{background:var(--surface2);border-color:var(--border2)}
369
+ .cdx-list--checklist .cdx-list__item--checked .cdx-list__item-checkbox{background:var(--accent);border-color:var(--accent)}
370
+ .cdx-list--checklist .cdx-list__item--checked .cdx-list__item-content{color:var(--text2);text-decoration:line-through}
371
+ .cdx-quote{border-left:3px solid var(--accent);padding-left:16px;font-style:italic;color:var(--text2);font-size:15px;line-height:1.65}
372
+ .cdx-quote [contenteditable]{outline:none}
373
+ .cdx-quote__caption{display:none!important}
374
+ .ce-delimiter{line-height:1;padding:12px 0;color:var(--text3);letter-spacing:8px}
375
+ .cdx-marker{background:rgba(91,110,245,.2);padding:2px 0}
376
+
377
+ /* Fallback textarea (when CDN not available) */
378
+ .editor-fallback-textarea{width:100%;min-height:200px;resize:vertical;background:transparent;border:none;color:var(--text);padding:0;font-family:var(--font);font-size:15px;line-height:1.65;outline:none}
379
+
380
+ /* ── Markdown Rendered (readonly) ────────────────────────── */
381
+ .md-rendered{font-size:15px;line-height:1.65;color:var(--text)}
382
+ .md-rendered h1,.md-rendered h2,.md-rendered h3{font-weight:700;margin:16px 0 6px}
383
+ .md-rendered h1{font-size:28px;line-height:1.2}
384
+ .md-rendered h2{font-size:22px;line-height:1.25}
385
+ .md-rendered h3{font-size:18px;line-height:1.3}
386
+ .md-rendered p{margin-bottom:8px}
387
+ .md-rendered ul,.md-rendered ol{padding-left:24px;margin-bottom:8px}
388
+ .md-rendered li{margin-bottom:4px}
389
+ .md-rendered code{background:var(--surface2);padding:2px 6px;border-radius:var(--radius-sm);font-family:var(--mono);font-size:13px}
390
+ .md-rendered pre{background:var(--surface);padding:16px;border-radius:var(--radius-sm);overflow-x:auto;margin-bottom:12px}
391
+ .md-rendered pre code{background:transparent;padding:0}
392
+ .md-rendered blockquote{border-left:3px solid var(--accent);padding-left:16px;color:var(--text2);margin:8px 0;font-style:italic}
393
+ .md-rendered hr{border:none;border-top:1px solid var(--border);margin:20px 0}
394
+ .md-rendered a{color:var(--accent)}
395
+ .md-rendered .md-check{display:flex;align-items:center;gap:8px;padding:2px 0}
396
+ .md-rendered .md-check-box{width:16px;height:16px;border:2px solid var(--border2);border-radius:3px;display:inline-flex;align-items:center;justify-content:center;font-size:11px;flex-shrink:0;color:var(--accent)}
397
+ .md-rendered .md-check-box.checked{background:var(--accent);border-color:var(--accent);color:#fff}
398
+
399
+ /* ── Settings Panel ──────────────────────────────────────── */
400
+ .sidebar-settings{padding:8px}
401
+ .sidebar-settings a{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:var(--radius-sm);color:var(--text3);font-size:13px;font-weight:500;transition:all .15s;cursor:pointer}
402
+ .sidebar-settings a:hover{color:var(--text);background:var(--surface2)}
403
+ .sidebar-settings svg{width:16px;height:16px;flex-shrink:0}
404
+ .settings-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:199;opacity:0;pointer-events:none;transition:opacity .2s}
405
+ .settings-overlay.open{opacity:1;pointer-events:auto}
406
+ .settings-panel{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) scale(.95);width:680px;max-width:92vw;max-height:85vh;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);z-index:200;display:flex;flex-direction:column;opacity:0;pointer-events:none;transition:all .2s ease;box-shadow:0 16px 48px rgba(0,0,0,.4)}
407
+ .settings-panel.open{opacity:1;pointer-events:auto;transform:translate(-50%,-50%) scale(1)}
408
+ .settings-header{padding:16px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;flex-shrink:0}
409
+ .settings-header h2{font-size:16px;font-weight:700}
410
+ .settings-content{flex:1;overflow-y:auto;padding:20px}
411
+ .settings-section h3{font-size:13px;font-weight:600;color:var(--text2);text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px}
412
+ .settings-theme-info{font-size:12px;color:var(--text3);margin-bottom:12px}
413
+ .theme-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px}
414
+ .theme-card{background:var(--surface);border:2px solid var(--border);border-radius:var(--radius);padding:10px;cursor:pointer;transition:all .15s}
415
+ .theme-card:hover{border-color:var(--border2)}
416
+ .theme-card.active{border-color:var(--accent)}
417
+ .theme-card-preview{display:flex;gap:3px;height:32px;border-radius:var(--radius-sm);overflow:hidden;margin-bottom:8px}
418
+ .theme-card-preview span{flex:1}
419
+ .theme-card-name{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
420
+ .theme-card-type{font-size:10px;color:var(--text3);text-transform:uppercase;letter-spacing:.03em}
421
+ .settings-footer{padding:14px 20px;border-top:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;flex-shrink:0;gap:12px}
422
+ .toggle-label{display:flex;align-items:center;gap:10px;cursor:pointer;font-size:13px;color:var(--text2);user-select:none}
423
+ .toggle-label input{display:none}
424
+ .toggle-switch{position:relative;width:36px;height:20px;background:var(--surface2);border:1px solid var(--border);border-radius:10px;transition:all .2s;flex-shrink:0}
425
+ .toggle-switch::after{content:'';position:absolute;top:2px;left:2px;width:14px;height:14px;background:var(--text3);border-radius:50%;transition:all .2s}
426
+ .toggle-label input:checked+.toggle-switch{background:var(--accent);border-color:var(--accent)}
427
+ .toggle-label input:checked+.toggle-switch::after{left:18px;background:#fff}
428
+
429
+ /* ── Rail Switch Icon ────────────────────────────────────── */
430
+ .rail-switch{background:transparent;color:var(--text3);border:1px dashed var(--border2)}
431
+ .rail-switch:hover{color:var(--text);border-color:var(--accent);background:var(--accent-dim);border-style:solid}
432
+
433
+ /* ── History Modal ──────────────────────────────────────── */
434
+ .history-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:199;opacity:0;pointer-events:none;transition:opacity .2s}
435
+ .history-overlay.open{opacity:1;pointer-events:auto}
436
+ .history-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) scale(.95);width:560px;max-width:92vw;max-height:70vh;background:var(--bg);border:1px solid var(--border);border-radius:12px;z-index:200;display:flex;flex-direction:column;opacity:0;pointer-events:none;transition:all .2s ease;box-shadow:0 16px 48px rgba(0,0,0,.5)}
437
+ .history-modal.open{opacity:1;pointer-events:auto;transform:translate(-50%,-50%) scale(1)}
438
+ .history-header{padding:16px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;flex-shrink:0}
439
+ .history-header h2{font-size:16px;font-weight:700}
440
+ .history-body{flex:1;overflow-y:auto;padding:8px 0}
441
+ .history-loading,.history-empty{padding:40px 20px;text-align:center;color:var(--text3);font-size:13px}
442
+ .history-item{display:flex;align-items:center;gap:12px;padding:10px 20px;cursor:pointer;transition:background .15s}
443
+ .history-item:hover{background:var(--surface2)}
444
+ .history-item-current{background:var(--accent-dim)}
445
+ .history-item-current:hover{background:var(--accent-dim)}
446
+ .history-item-icon{width:36px;height:36px;border-radius:var(--radius);background:var(--surface2);display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:700;color:var(--accent);flex-shrink:0}
447
+ .history-item-current .history-item-icon{background:var(--accent);color:#fff}
448
+ .history-item-info{flex:1;min-width:0}
449
+ .history-item-name{font-size:13px;font-weight:600;display:flex;align-items:center;gap:8px}
450
+ .history-item-path{font-size:11px;color:var(--text3);font-family:var(--mono);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
451
+ .history-item-time{font-size:11px;color:var(--text3);font-family:var(--mono);flex-shrink:0;white-space:nowrap}
452
+ .history-current-badge{display:inline-block;padding:1px 8px;border-radius:10px;font-size:10px;font-weight:600;background:var(--accent);color:#fff}
453
+
246
454
  /* ── Responsive ──────────────────────────────────────────── */
247
455
  @media(max-width:1024px){
248
456
  .source-rail{width:48px;padding:8px 0;gap:6px}
@@ -255,15 +463,22 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
255
463
  .header{padding:12px 16px}
256
464
  .content{padding:16px}
257
465
  .detail-panel{width:100%;max-width:100%}
466
+ #view-notes.active{margin:-16px}
467
+ .notes-sidebar{width:200px}
468
+ .notes-header-bar{padding:24px 24px 0}
469
+ .notes-editorjs-wrap{padding:12px 16px 24px}
258
470
  }
259
471
  @media(max-width:768px){
260
472
  #h-stats{flex-wrap:wrap;gap:12px}
261
473
  .header{gap:12px}
262
474
  .metrics-grid{grid-template-columns:1fr}
263
- .panel-props{grid-template-columns:1fr}
475
+ .notes-sidebar{display:none}
476
+ .notes-header-bar{padding:20px 16px 0}
477
+ .notes-editorjs-wrap{padding:8px 8px 24px}
264
478
  }
265
479
  </style>
266
480
  <style id="dynamic-styles"></style>
481
+ <style id="theme-styles"></style>
267
482
  <link id="custom-theme" rel="stylesheet" href="/mdboard.css">
268
483
  </head>
269
484
  <body>
@@ -294,7 +509,17 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
294
509
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M18 20V10M12 20V4M6 20v-6"/></svg>
295
510
  <span>Metrics</span>
296
511
  </a>
512
+ <a href="#notes" data-view="notes">
513
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><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"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
514
+ <span>Notes</span>
515
+ </a>
297
516
  </nav>
517
+ <div class="sidebar-settings" id="sidebar-settings">
518
+ <a href="#" id="settings-toggle">
519
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 01-2.83 2.83l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg>
520
+ <span>Settings</span>
521
+ </a>
522
+ </div>
298
523
  <div class="sidebar-footer">mdboard</div>
299
524
  </aside>
300
525
 
@@ -335,6 +560,9 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
335
560
  <div id="view-overview" class="view">
336
561
  <div id="overview-container"></div>
337
562
  </div>
563
+ <div id="view-notes" class="view">
564
+ <div id="notes-container"></div>
565
+ </div>
338
566
  </div>
339
567
  </div>
340
568
  </div>
@@ -343,7 +571,306 @@ th.sorted .sort-arrow{opacity:1;color:var(--accent)}
343
571
  <div id="panel-overlay" class="panel-overlay"></div>
344
572
  <div id="detail-panel" class="detail-panel"></div>
345
573
 
574
+ <!-- Settings Panel -->
575
+ <div id="settings-overlay" class="settings-overlay"></div>
576
+ <div id="settings-panel" class="settings-panel">
577
+ <div class="settings-header">
578
+ <h2>Settings</h2>
579
+ <button class="panel-close" id="settings-close">&times;</button>
580
+ </div>
581
+ <div class="settings-content">
582
+ <div class="settings-section">
583
+ <h3>Theme</h3>
584
+ <p id="settings-theme-info" class="settings-theme-info"></p>
585
+ <div id="theme-grid" class="theme-grid"></div>
586
+ </div>
587
+ </div>
588
+ <div class="settings-footer">
589
+ <label class="toggle-label">
590
+ <input type="checkbox" id="theme-default-toggle">
591
+ <span class="toggle-switch"></span>
592
+ <span>Set as default for all projects</span>
593
+ </label>
594
+ <button class="btn btn-primary" id="settings-save">Save</button>
595
+ </div>
596
+ </div>
597
+
598
+ <!-- History Modal -->
599
+ <div id="history-overlay" class="history-overlay"></div>
600
+ <div id="history-modal" class="history-modal"></div>
601
+
602
+ <script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@2/dist/editorjs.umd.js"></script>
603
+ <script src="https://cdn.jsdelivr.net/npm/@editorjs/header@latest/dist/header.umd.js"></script>
604
+ <script src="https://cdn.jsdelivr.net/npm/@editorjs/list@latest/dist/editorjs-list.umd.js"></script>
605
+ <script src="https://cdn.jsdelivr.net/npm/@editorjs/code@latest/dist/code.umd.js"></script>
606
+ <script src="https://cdn.jsdelivr.net/npm/@editorjs/quote@latest/dist/quote.umd.js"></script>
607
+ <script src="https://cdn.jsdelivr.net/npm/@editorjs/delimiter@latest/dist/delimiter.umd.js"></script>
608
+ <script src="https://cdn.jsdelivr.net/npm/@editorjs/marker@latest/dist/marker.umd.js"></script>
609
+ <script src="https://cdn.jsdelivr.net/npm/@editorjs/inline-code@latest/dist/inline-code.umd.js"></script>
346
610
  <script>
611
+ /* ══════════════════════════════════════════════════════════════
612
+ THEMES — Predefined color themes for mdboard
613
+ ══════════════════════════════════════════════════════════════ */
614
+ var THEMES = {
615
+ 'default-dark': {
616
+ name: 'Default Dark', type: 'dark',
617
+ vars: {
618
+ bg:'#0A0A0B',surface:'#141415',surface2:'#1A1A1C',border:'#232326',border2:'#2E2E33',
619
+ text:'#E8E8ED',text2:'#8B8B93',text3:'#5A5A63',
620
+ accent:'#5B6EF5','accent-dim':'rgba(91,110,245,.15)',
621
+ success:'#2EA043','success-dim':'rgba(46,160,67,.15)',
622
+ warning:'#D4A72C','warning-dim':'rgba(212,167,44,.15)',
623
+ danger:'#DA3633','danger-dim':'rgba(218,54,51,.15)',
624
+ purple:'#8B5CF6','purple-dim':'rgba(139,92,246,.15)'
625
+ }
626
+ },
627
+ 'linear-dark': {
628
+ name: 'Linear Dark', type: 'dark',
629
+ vars: {
630
+ bg:'#101012',surface:'#1A1A1F',surface2:'#222228',border:'#2C2C35',border2:'#38384A',
631
+ text:'#E2E2E9',text2:'#8E8E9E',text3:'#5C5C6E',
632
+ accent:'#5E6AD2','accent-dim':'rgba(94,106,210,.15)',
633
+ success:'#4DA771','success-dim':'rgba(77,167,113,.15)',
634
+ warning:'#F2C94C','warning-dim':'rgba(242,201,76,.15)',
635
+ danger:'#EB5757','danger-dim':'rgba(235,87,87,.15)',
636
+ purple:'#BB87FC','purple-dim':'rgba(187,135,252,.15)'
637
+ }
638
+ },
639
+ 'linear-light': {
640
+ name: 'Linear Light', type: 'light',
641
+ vars: {
642
+ bg:'#F9F9FB',surface:'#FFFFFF',surface2:'#F0F0F4',border:'#E0E0E6',border2:'#D0D0D8',
643
+ text:'#1A1A2E',text2:'#6B6B80',text3:'#9090A0',
644
+ accent:'#5E6AD2','accent-dim':'rgba(94,106,210,.1)',
645
+ success:'#4DA771','success-dim':'rgba(77,167,113,.1)',
646
+ warning:'#D4A017','warning-dim':'rgba(212,160,23,.1)',
647
+ danger:'#EB5757','danger-dim':'rgba(235,87,87,.1)',
648
+ purple:'#9B6DD7','purple-dim':'rgba(155,109,215,.1)'
649
+ }
650
+ },
651
+ 'jira-dark': {
652
+ name: 'Jira Dark', type: 'dark',
653
+ vars: {
654
+ bg:'#0D1117',surface:'#161B22',surface2:'#1C2333',border:'#293040',border2:'#344050',
655
+ text:'#DEE4EC',text2:'#8C96A5',text3:'#5A6577',
656
+ accent:'#2684FF','accent-dim':'rgba(38,132,255,.15)',
657
+ success:'#36B37E','success-dim':'rgba(54,179,126,.15)',
658
+ warning:'#FFAB00','warning-dim':'rgba(255,171,0,.15)',
659
+ danger:'#FF5630','danger-dim':'rgba(255,86,48,.15)',
660
+ purple:'#6554C0','purple-dim':'rgba(101,84,192,.15)'
661
+ }
662
+ },
663
+ 'jira-light': {
664
+ name: 'Jira Light', type: 'light',
665
+ vars: {
666
+ bg:'#F4F5F7',surface:'#FFFFFF',surface2:'#EBECF0',border:'#DFE1E6',border2:'#C1C7D0',
667
+ text:'#172B4D',text2:'#6B778C',text3:'#97A0AF',
668
+ accent:'#0052CC','accent-dim':'rgba(0,82,204,.1)',
669
+ success:'#36B37E','success-dim':'rgba(54,179,126,.1)',
670
+ warning:'#FF991F','warning-dim':'rgba(255,153,31,.1)',
671
+ danger:'#FF5630','danger-dim':'rgba(255,86,48,.1)',
672
+ purple:'#6554C0','purple-dim':'rgba(101,84,192,.1)'
673
+ }
674
+ },
675
+ 'catppuccin-mocha': {
676
+ name: 'Catppuccin Mocha', type: 'dark',
677
+ vars: {
678
+ bg:'#1E1E2E',surface:'#262637',surface2:'#313244',border:'#3B3B52',border2:'#45475A',
679
+ text:'#CDD6F4',text2:'#A6ADC8',text3:'#6C7086',
680
+ accent:'#89B4FA','accent-dim':'rgba(137,180,250,.15)',
681
+ success:'#A6E3A1','success-dim':'rgba(166,227,161,.15)',
682
+ warning:'#F9E2AF','warning-dim':'rgba(249,226,175,.15)',
683
+ danger:'#F38BA8','danger-dim':'rgba(243,139,168,.15)',
684
+ purple:'#CBA6F7','purple-dim':'rgba(203,166,247,.15)'
685
+ }
686
+ },
687
+ 'catppuccin-latte': {
688
+ name: 'Catppuccin Latte', type: 'light',
689
+ vars: {
690
+ bg:'#EFF1F5',surface:'#FFFFFF',surface2:'#E6E9EF',border:'#CCD0DA',border2:'#BCC0CC',
691
+ text:'#4C4F69',text2:'#6C6F85',text3:'#9CA0B0',
692
+ accent:'#1E66F5','accent-dim':'rgba(30,102,245,.1)',
693
+ success:'#40A02B','success-dim':'rgba(64,160,43,.1)',
694
+ warning:'#DF8E1D','warning-dim':'rgba(223,142,29,.1)',
695
+ danger:'#D20F39','danger-dim':'rgba(210,15,57,.1)',
696
+ purple:'#8839EF','purple-dim':'rgba(136,57,239,.1)'
697
+ }
698
+ },
699
+ 'ayu-dark': {
700
+ name: 'Ayu Dark', type: 'dark',
701
+ vars: {
702
+ bg:'#0B0E14',surface:'#0F1219',surface2:'#151920',border:'#1E222A',border2:'#272D38',
703
+ text:'#BFBDB6',text2:'#6C7380',text3:'#4A5060',
704
+ accent:'#E6B450','accent-dim':'rgba(230,180,80,.15)',
705
+ success:'#7FD962','success-dim':'rgba(127,217,98,.15)',
706
+ warning:'#FFB454','warning-dim':'rgba(255,180,84,.15)',
707
+ danger:'#F07178','danger-dim':'rgba(240,113,120,.15)',
708
+ purple:'#D2A6FF','purple-dim':'rgba(210,166,255,.15)'
709
+ }
710
+ },
711
+ 'ayu-light': {
712
+ name: 'Ayu Light', type: 'light',
713
+ vars: {
714
+ bg:'#F8F9FA',surface:'#FFFFFF',surface2:'#F0F1F2',border:'#E0E1E2',border2:'#D4D5D6',
715
+ text:'#5C6166',text2:'#787B80',text3:'#ACB0B5',
716
+ accent:'#FF9940','accent-dim':'rgba(255,153,64,.1)',
717
+ success:'#6CBF43','success-dim':'rgba(108,191,67,.1)',
718
+ warning:'#F2AE49','warning-dim':'rgba(242,174,73,.1)',
719
+ danger:'#F07171','danger-dim':'rgba(240,113,113,.1)',
720
+ purple:'#A37ACC','purple-dim':'rgba(163,122,204,.1)'
721
+ }
722
+ },
723
+ 'tokyo-night': {
724
+ name: 'Tokyo Night', type: 'dark',
725
+ vars: {
726
+ bg:'#1A1B26',surface:'#1F2335',surface2:'#24283B',border:'#2F3449',border2:'#3B4261',
727
+ text:'#C0CAF5',text2:'#7982A9',text3:'#565F89',
728
+ accent:'#7AA2F7','accent-dim':'rgba(122,162,247,.15)',
729
+ success:'#9ECE6A','success-dim':'rgba(158,206,106,.15)',
730
+ warning:'#E0AF68','warning-dim':'rgba(224,175,104,.15)',
731
+ danger:'#F7768E','danger-dim':'rgba(247,118,142,.15)',
732
+ purple:'#BB9AF7','purple-dim':'rgba(187,154,247,.15)'
733
+ }
734
+ },
735
+ 'tokyo-night-storm': {
736
+ name: 'Tokyo Night Storm', type: 'dark',
737
+ vars: {
738
+ bg:'#24283B',surface:'#292E42',surface2:'#2F3549',border:'#3B4261',border2:'#464F78',
739
+ text:'#C0CAF5',text2:'#7982A9',text3:'#565F89',
740
+ accent:'#7AA2F7','accent-dim':'rgba(122,162,247,.15)',
741
+ success:'#9ECE6A','success-dim':'rgba(158,206,106,.15)',
742
+ warning:'#E0AF68','warning-dim':'rgba(224,175,104,.15)',
743
+ danger:'#F7768E','danger-dim':'rgba(247,118,142,.15)',
744
+ purple:'#BB9AF7','purple-dim':'rgba(187,154,247,.15)'
745
+ }
746
+ },
747
+ 'notion-light': {
748
+ name: 'Notion Light', type: 'light',
749
+ vars: {
750
+ bg:'#F7F6F3',surface:'#FFFFFF',surface2:'#EEEEEC',border:'#E3E2DE',border2:'#D4D3CF',
751
+ text:'#37352F',text2:'#6B6B6B',text3:'#9B9A97',
752
+ accent:'#2383E2','accent-dim':'rgba(35,131,226,.1)',
753
+ success:'#0F7B0F','success-dim':'rgba(15,123,15,.1)',
754
+ warning:'#CB7B00','warning-dim':'rgba(203,123,0,.1)',
755
+ danger:'#E03E3E','danger-dim':'rgba(224,62,62,.1)',
756
+ purple:'#6940A5','purple-dim':'rgba(105,64,165,.1)'
757
+ }
758
+ },
759
+ 'notion-dark': {
760
+ name: 'Notion Dark', type: 'dark',
761
+ vars: {
762
+ bg:'#191919',surface:'#202020',surface2:'#2B2B2B',border:'#363636',border2:'#444444',
763
+ text:'#E3E2DE',text2:'#9B9B9B',text3:'#6B6B6B',
764
+ accent:'#529CCA','accent-dim':'rgba(82,156,202,.15)',
765
+ success:'#4DAB9A','success-dim':'rgba(77,171,154,.15)',
766
+ warning:'#CB7B00','warning-dim':'rgba(203,123,0,.15)',
767
+ danger:'#E03E3E','danger-dim':'rgba(224,62,62,.15)',
768
+ purple:'#9A6DD7','purple-dim':'rgba(154,109,215,.15)'
769
+ }
770
+ },
771
+ 'github-dark': {
772
+ name: 'GitHub Dark', type: 'dark',
773
+ vars: {
774
+ bg:'#0D1117',surface:'#161B22',surface2:'#1C2128',border:'#30363D',border2:'#3D444D',
775
+ text:'#E6EDF3',text2:'#8B949E',text3:'#6E7681',
776
+ accent:'#58A6FF','accent-dim':'rgba(88,166,255,.15)',
777
+ success:'#3FB950','success-dim':'rgba(63,185,80,.15)',
778
+ warning:'#D29922','warning-dim':'rgba(210,153,34,.15)',
779
+ danger:'#F85149','danger-dim':'rgba(248,81,73,.15)',
780
+ purple:'#BC8CFF','purple-dim':'rgba(188,140,255,.15)'
781
+ }
782
+ },
783
+ 'github-light': {
784
+ name: 'GitHub Light', type: 'light',
785
+ vars: {
786
+ bg:'#F6F8FA',surface:'#FFFFFF',surface2:'#EAEEF2',border:'#D0D7DE',border2:'#BCC3CB',
787
+ text:'#1F2328',text2:'#656D76',text3:'#8C959F',
788
+ accent:'#0969DA','accent-dim':'rgba(9,105,218,.1)',
789
+ success:'#1A7F37','success-dim':'rgba(26,127,55,.1)',
790
+ warning:'#9A6700','warning-dim':'rgba(154,103,0,.1)',
791
+ danger:'#CF222E','danger-dim':'rgba(207,34,46,.1)',
792
+ purple:'#8250DF','purple-dim':'rgba(130,80,223,.1)'
793
+ }
794
+ },
795
+ 'dracula': {
796
+ name: 'Dracula', type: 'dark',
797
+ vars: {
798
+ bg:'#1E1F29',surface:'#282A36',surface2:'#2E303E',border:'#3A3C4E',border2:'#44475A',
799
+ text:'#F8F8F2',text2:'#ADB0C0',text3:'#6272A4',
800
+ accent:'#BD93F9','accent-dim':'rgba(189,147,249,.15)',
801
+ success:'#50FA7B','success-dim':'rgba(80,250,123,.15)',
802
+ warning:'#F1FA8C','warning-dim':'rgba(241,250,140,.15)',
803
+ danger:'#FF5555','danger-dim':'rgba(255,85,85,.15)',
804
+ purple:'#FF79C6','purple-dim':'rgba(255,121,198,.15)'
805
+ }
806
+ },
807
+ 'nord': {
808
+ name: 'Nord', type: 'dark',
809
+ vars: {
810
+ bg:'#242933',surface:'#2E3440',surface2:'#353C4A',border:'#3B4252',border2:'#434C5E',
811
+ text:'#ECEFF4',text2:'#A5B1C2',text3:'#697688',
812
+ accent:'#88C0D0','accent-dim':'rgba(136,192,208,.15)',
813
+ success:'#A3BE8C','success-dim':'rgba(163,190,140,.15)',
814
+ warning:'#EBCB8B','warning-dim':'rgba(235,203,139,.15)',
815
+ danger:'#BF616A','danger-dim':'rgba(191,97,106,.15)',
816
+ purple:'#B48EAD','purple-dim':'rgba(180,142,173,.15)'
817
+ }
818
+ },
819
+ 'solarized-dark': {
820
+ name: 'Solarized Dark', type: 'dark',
821
+ vars: {
822
+ bg:'#002129',surface:'#002B36',surface2:'#073642',border:'#0A4050',border2:'#1A5060',
823
+ text:'#93A1A1',text2:'#839496',text3:'#586E75',
824
+ accent:'#268BD2','accent-dim':'rgba(38,139,210,.15)',
825
+ success:'#859900','success-dim':'rgba(133,153,0,.15)',
826
+ warning:'#B58900','warning-dim':'rgba(181,137,0,.15)',
827
+ danger:'#DC322F','danger-dim':'rgba(220,50,47,.15)',
828
+ purple:'#6C71C4','purple-dim':'rgba(108,113,196,.15)'
829
+ }
830
+ },
831
+ 'solarized-light': {
832
+ name: 'Solarized Light', type: 'light',
833
+ vars: {
834
+ bg:'#FDF6E3',surface:'#FFFFFF',surface2:'#EEE8D5',border:'#DDD6C1',border2:'#C9C2AD',
835
+ text:'#586E75',text2:'#657B83',text3:'#93A1A1',
836
+ accent:'#268BD2','accent-dim':'rgba(38,139,210,.1)',
837
+ success:'#859900','success-dim':'rgba(133,153,0,.1)',
838
+ warning:'#B58900','warning-dim':'rgba(181,137,0,.1)',
839
+ danger:'#DC322F','danger-dim':'rgba(220,50,47,.1)',
840
+ purple:'#6C71C4','purple-dim':'rgba(108,113,196,.1)'
841
+ }
842
+ },
843
+ 'one-dark': {
844
+ name: 'One Dark', type: 'dark',
845
+ vars: {
846
+ bg:'#1E2127',surface:'#282C34',surface2:'#2C313C',border:'#3A3F4B',border2:'#4B5263',
847
+ text:'#ABB2BF',text2:'#7F848E',text3:'#5C6370',
848
+ accent:'#61AFEF','accent-dim':'rgba(97,175,239,.15)',
849
+ success:'#98C379','success-dim':'rgba(152,195,121,.15)',
850
+ warning:'#E5C07B','warning-dim':'rgba(229,192,123,.15)',
851
+ danger:'#E06C75','danger-dim':'rgba(224,108,117,.15)',
852
+ purple:'#C678DD','purple-dim':'rgba(198,120,221,.15)'
853
+ }
854
+ },
855
+ 'gruvbox-dark': {
856
+ name: 'Gruvbox Dark', type: 'dark',
857
+ vars: {
858
+ bg:'#1D2021',surface:'#282828',surface2:'#32302F',border:'#3C3836',border2:'#504945',
859
+ text:'#EBDBB2',text2:'#A89984',text3:'#7C6F64',
860
+ accent:'#FE8019','accent-dim':'rgba(254,128,25,.15)',
861
+ success:'#B8BB26','success-dim':'rgba(184,187,38,.15)',
862
+ warning:'#FABD2F','warning-dim':'rgba(250,189,47,.15)',
863
+ danger:'#FB4934','danger-dim':'rgba(251,73,52,.15)',
864
+ purple:'#D3869B','purple-dim':'rgba(211,134,155,.15)'
865
+ }
866
+ }
867
+ };
868
+
869
+ var THEME_LIST = Object.keys(THEMES).map(function(id) {
870
+ return { id: id, name: THEMES[id].name, type: THEMES[id].type };
871
+ });
872
+
873
+
347
874
  /* ══════════════════════════════════════════════════════════════
348
875
  ICON SHAPES — SVG generators by icon name
349
876
  ══════════════════════════════════════════════════════════════ */
@@ -416,7 +943,8 @@ function milestoneIcon(status) {
416
943
  var D = {
417
944
  config: null, project: null, milestones: [], epics: [], tasks: [],
418
945
  sprints: [], allSprints: [], metrics: null, health: null, loaded: false,
419
- sources: [], activeSource: null, overviewLinks: null
946
+ sources: [], activeSource: null, overviewLinks: null, notes: [],
947
+ aiSuggestions: null, theme: null
420
948
  };
421
949
 
422
950
  /* ── Helpers ─────────────────────────────────────────────── */
@@ -539,7 +1067,7 @@ async function loadAll() {
539
1067
  fetchJson(base + '/project'), fetchJson(base + '/milestones'), fetchJson(base + '/epics'),
540
1068
  fetchJson(base + '/tasks'), fetchJson(base + '/sprint'), fetchJson(base + '/metrics'),
541
1069
  fetchJson(base + '/health'), fetchJson(base + '/sprints'), fetchJson('/api/config'),
542
- fetchJson('/api/sources')
1070
+ fetchJson('/api/sources'), fetchJson(base + '/notes')
543
1071
  ]);
544
1072
  D.project = results[0]; D.milestones = results[1] || []; D.epics = results[2] || [];
545
1073
  D.tasks = results[3] || [];
@@ -548,6 +1076,7 @@ async function loadAll() {
548
1076
  D.allSprints = results[7] || [];
549
1077
  D.config = results[8];
550
1078
  D.sources = results[9] || [];
1079
+ D.notes = results[10] || [];
551
1080
  D.loaded = true;
552
1081
  }
553
1082
 
@@ -637,6 +1166,43 @@ function generateDynamicStyles() {
637
1166
  if (styleEl) styleEl.textContent = css;
638
1167
  }
639
1168
 
1169
+ /* ══════════════════════════════════════════════════════════════
1170
+ THEME ENGINE
1171
+ ══════════════════════════════════════════════════════════════ */
1172
+ function generateThemeCss(themeId) {
1173
+ var theme = THEMES[themeId];
1174
+ if (!theme) return '';
1175
+ var css = '/* mdboard theme: ' + theme.name + ' */\n:root {\n';
1176
+ for (var key in theme.vars) {
1177
+ if (theme.vars.hasOwnProperty(key)) {
1178
+ css += ' --' + key + ': ' + theme.vars[key] + ';\n';
1179
+ }
1180
+ }
1181
+ css += '}\n';
1182
+ return css;
1183
+ }
1184
+
1185
+ function applyTheme(themeId) {
1186
+ var theme = THEMES[themeId];
1187
+ if (!theme) return;
1188
+ var el = document.getElementById('theme-styles');
1189
+ if (el) el.textContent = generateThemeCss(themeId);
1190
+ }
1191
+
1192
+ function revertThemePreview() {
1193
+ var el = document.getElementById('theme-styles');
1194
+ if (el) el.textContent = '';
1195
+ }
1196
+
1197
+ function reloadCustomCss() {
1198
+ var link = document.getElementById('custom-theme');
1199
+ if (link) link.href = '/mdboard.css?t=' + Date.now();
1200
+ }
1201
+
1202
+ function getActiveTheme() {
1203
+ return D.config && D.config.theme ? D.config.theme : 'default-dark';
1204
+ }
1205
+
640
1206
  /* ══════════════════════════════════════════════════════════════
641
1207
  WORKSPACE — Source switching & sidebar
642
1208
  ══════════════════════════════════════════════════════════════ */
@@ -703,6 +1269,19 @@ function buildSourceRail() {
703
1269
  })(s.name);
704
1270
  rail.appendChild(icon);
705
1271
  }
1272
+
1273
+ // Switch project icon (bottom of rail)
1274
+ var switchDivider = document.createElement('div');
1275
+ switchDivider.className = 'rail-divider';
1276
+ switchDivider.style.marginTop = 'auto';
1277
+ rail.appendChild(switchDivider);
1278
+
1279
+ var switchIcon = document.createElement('div');
1280
+ switchIcon.className = 'rail-icon rail-switch';
1281
+ switchIcon.setAttribute('data-tooltip', 'Switch workspace');
1282
+ switchIcon.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 3h5v5"/><path d="M21 3l-7 7"/><path d="M8 21H3v-5"/><path d="M3 21l7-7"/></svg>';
1283
+ switchIcon.onclick = function() { openHistoryModal(); };
1284
+ rail.appendChild(switchIcon);
706
1285
  }
707
1286
 
708
1287
  function switchSource(sourceName) {
@@ -757,6 +1336,28 @@ function connectSSE() {
757
1336
  src.onmessage = function(e) {
758
1337
  var data;
759
1338
  try { data = JSON.parse(e.data); } catch { data = {}; }
1339
+
1340
+ if (data.type === 'project-changed') {
1341
+ // Full reload on project switch (from another tab or API call)
1342
+ D.activeSource = null;
1343
+ D.loaded = false;
1344
+ loadAll().then(function() {
1345
+ initFromConfig();
1346
+ if (hasWorkspace()) {
1347
+ buildSourceRail();
1348
+ D.activeSource = null;
1349
+ switchSource('overview');
1350
+ } else {
1351
+ // Hide source rail if no workspace
1352
+ var rail = document.getElementById('source-rail');
1353
+ if (rail) rail.innerHTML = '';
1354
+ renderAll();
1355
+ }
1356
+ renderSidebarLogo();
1357
+ });
1358
+ return;
1359
+ }
1360
+
760
1361
  if (data.cssReload) {
761
1362
  var link = document.getElementById('custom-theme');
762
1363
  if (link) link.href = '/mdboard.css?' + Date.now();
@@ -768,51 +1369,325 @@ function connectSSE() {
768
1369
  }
769
1370
 
770
1371
  /* ══════════════════════════════════════════════════════════════
771
- RENDER
1372
+ EDITOR.JS WRAPPER — md↔blocks conversion + Notion-like UX
772
1373
  ══════════════════════════════════════════════════════════════ */
773
- function renderAll() {
774
- renderHeader(); renderBoardFilters(); renderBoard(); renderTableControls(); renderTableBody(); renderMsFilters(); renderMilestones(); renderMetrics();
775
- // Render overview if active
776
- var overviewView = document.getElementById('view-overview');
777
- if (overviewView && overviewView.classList.contains('active')) {
778
- renderOverview();
1374
+
1375
+ /* ── Inline Markdown HTML ──────────────────────────────── */
1376
+ function inlineMdToHtml(text) {
1377
+ if (!text) return '';
1378
+ text = text.replace(/\*\*(.+?)\*\*/g, '<b>$1</b>');
1379
+ text = text.replace(/\*(.+?)\*/g, '<i>$1</i>');
1380
+ text = text.replace(/`([^`]+)`/g, '<code>$1</code>');
1381
+ text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
1382
+ return text;
1383
+ }
1384
+
1385
+ function inlineHtmlToMd(html) {
1386
+ if (!html) return '';
1387
+ var text = html.replace(/<br\s*\/?>/gi, '\n');
1388
+ text = text.replace(/<a[^>]+href="([^"]*)"[^>]*>([^<]*)<\/a>/gi, '[$2]($1)');
1389
+ text = text.replace(/<b>([^<]*)<\/b>/gi, '**$1**');
1390
+ text = text.replace(/<strong>([^<]*)<\/strong>/gi, '**$1**');
1391
+ text = text.replace(/<i>([^<]*)<\/i>/gi, '*$1*');
1392
+ text = text.replace(/<em>([^<]*)<\/em>/gi, '*$1*');
1393
+ text = text.replace(/<code>([^<]*)<\/code>/gi, '`$1`');
1394
+ text = text.replace(/<mark[^>]*>([^<]*)<\/mark>/gi, '==$1==');
1395
+ text = text.replace(/<[^>]+>/g, '');
1396
+ return text;
1397
+ }
1398
+
1399
+ /* ── Markdown → Editor.js Blocks ────────────────────────── */
1400
+ function markdownToBlocks(md) {
1401
+ if (!md || !md.trim()) return [{ type: 'paragraph', data: { text: '' } }];
1402
+
1403
+ var lines = md.split('\n');
1404
+ var blocks = [];
1405
+ var i = 0;
1406
+
1407
+ while (i < lines.length) {
1408
+ var line = lines[i];
1409
+
1410
+ if (line.trim() === '') { i++; continue; }
1411
+
1412
+ if (/^---\s*$/.test(line.trim()) || /^\*\*\*\s*$/.test(line.trim())) {
1413
+ blocks.push({ type: 'delimiter', data: {} });
1414
+ i++; continue;
1415
+ }
1416
+
1417
+ var hMatch = line.match(/^(#{1,6})\s+(.+)$/);
1418
+ if (hMatch) {
1419
+ var lvl = Math.min(hMatch[1].length, 6);
1420
+ blocks.push({ type: 'header', data: { text: inlineMdToHtml(hMatch[2]), level: lvl } });
1421
+ i++; continue;
1422
+ }
1423
+
1424
+ if (line.trim().startsWith('```')) {
1425
+ var code = [];
1426
+ i++;
1427
+ while (i < lines.length && !lines[i].trim().startsWith('```')) {
1428
+ code.push(lines[i]);
1429
+ i++;
1430
+ }
1431
+ blocks.push({ type: 'code', data: { code: code.join('\n') } });
1432
+ i++; continue;
1433
+ }
1434
+
1435
+ if (line.trim().startsWith('> ')) {
1436
+ var quoteLines = [];
1437
+ while (i < lines.length && lines[i].trim().startsWith('> ')) {
1438
+ quoteLines.push(lines[i].trim().substring(2));
1439
+ i++;
1440
+ }
1441
+ blocks.push({ type: 'quote', data: { text: inlineMdToHtml(quoteLines.join('<br>')), caption: '' } });
1442
+ continue;
1443
+ }
1444
+
1445
+ if (/^\s*-\s+\[[ x]\]\s+/.test(line)) {
1446
+ var checkItems = [];
1447
+ while (i < lines.length && /^\s*-\s+\[[ x]\]\s+/.test(lines[i])) {
1448
+ var cm = lines[i].match(/^\s*-\s+\[([ x])\]\s+(.+)$/);
1449
+ if (cm) checkItems.push({ content: inlineMdToHtml(cm[2]), meta: { checked: cm[1] === 'x' }, items: [] });
1450
+ i++;
1451
+ }
1452
+ blocks.push({ type: 'list', data: { style: 'checklist', items: checkItems } });
1453
+ continue;
1454
+ }
1455
+
1456
+ if (/^\s*[-*]\s+/.test(line) && !/^\s*-\s+\[/.test(line)) {
1457
+ var listItems = [];
1458
+ while (i < lines.length && /^\s*[-*]\s+/.test(lines[i]) && !/^\s*-\s+\[/.test(lines[i])) {
1459
+ var lm = lines[i].match(/^\s*[-*]\s+(.+)$/);
1460
+ if (lm) listItems.push({ content: inlineMdToHtml(lm[1]), items: [] });
1461
+ i++;
1462
+ }
1463
+ blocks.push({ type: 'list', data: { style: 'unordered', items: listItems } });
1464
+ continue;
1465
+ }
1466
+
1467
+ if (/^\s*\d+\.\s+/.test(line)) {
1468
+ var olItems = [];
1469
+ while (i < lines.length && /^\s*\d+\.\s+/.test(lines[i])) {
1470
+ var om = lines[i].match(/^\s*\d+\.\s+(.+)$/);
1471
+ if (om) olItems.push({ content: inlineMdToHtml(om[1]), items: [] });
1472
+ i++;
1473
+ }
1474
+ blocks.push({ type: 'list', data: { style: 'ordered', items: olItems } });
1475
+ continue;
1476
+ }
1477
+
1478
+ var paraLines = [];
1479
+ while (i < lines.length && lines[i].trim() !== '' &&
1480
+ !/^#{1,6}\s/.test(lines[i]) && !/^```/.test(lines[i]) &&
1481
+ !/^>\s/.test(lines[i]) && !/^\s*[-*]\s/.test(lines[i]) &&
1482
+ !/^\s*\d+\.\s/.test(lines[i]) && !/^---\s*$/.test(lines[i].trim()) &&
1483
+ !/^\*\*\*\s*$/.test(lines[i].trim())) {
1484
+ paraLines.push(lines[i]);
1485
+ i++;
1486
+ }
1487
+ if (paraLines.length > 0) {
1488
+ blocks.push({ type: 'paragraph', data: { text: inlineMdToHtml(paraLines.join('<br>')) } });
1489
+ }
779
1490
  }
1491
+
1492
+ return blocks.length > 0 ? blocks : [{ type: 'paragraph', data: { text: '' } }];
780
1493
  }
781
1494
 
782
- function renderHeader() {
783
- renderSidebarLogo();
1495
+ /* ── Editor.js Blocks → Markdown ────────────────────────── */
1496
+ function blocksToMarkdown(blocks) {
1497
+ if (!blocks || blocks.length === 0) return '';
1498
+
1499
+ var parts = [];
1500
+ for (var i = 0; i < blocks.length; i++) {
1501
+ var b = blocks[i];
1502
+ switch (b.type) {
1503
+ case 'header':
1504
+ var prefix = '';
1505
+ for (var h = 0; h < (b.data.level || 2); h++) prefix += '#';
1506
+ parts.push(prefix + ' ' + inlineHtmlToMd(b.data.text));
1507
+ break;
1508
+ case 'paragraph':
1509
+ parts.push(inlineHtmlToMd(b.data.text));
1510
+ break;
1511
+ case 'list':
1512
+ var items = b.data.items || [];
1513
+ var listLines = [];
1514
+ for (var li = 0; li < items.length; li++) {
1515
+ var itemText = typeof items[li] === 'string' ? items[li] : (items[li].content || items[li].text || '');
1516
+ if (b.data.style === 'checklist') {
1517
+ var chk = (typeof items[li] === 'object' && items[li].meta && items[li].meta.checked) ? 'x' : ' ';
1518
+ listLines.push('- [' + chk + '] ' + inlineHtmlToMd(itemText));
1519
+ } else if (b.data.style === 'ordered') {
1520
+ listLines.push((li + 1) + '. ' + inlineHtmlToMd(itemText));
1521
+ } else {
1522
+ listLines.push('- ' + inlineHtmlToMd(itemText));
1523
+ }
1524
+ }
1525
+ parts.push(listLines.join('\n'));
1526
+ break;
1527
+ case 'checklist':
1528
+ var cItems = b.data.items || [];
1529
+ var checkLines = [];
1530
+ for (var ci = 0; ci < cItems.length; ci++) {
1531
+ var check = cItems[ci].checked ? 'x' : ' ';
1532
+ checkLines.push('- [' + check + '] ' + inlineHtmlToMd(cItems[ci].text || cItems[ci].content || ''));
1533
+ }
1534
+ parts.push(checkLines.join('\n'));
1535
+ break;
1536
+ case 'code':
1537
+ parts.push('```\n' + (b.data.code || '') + '\n```');
1538
+ break;
1539
+ case 'quote':
1540
+ var qText = inlineHtmlToMd(b.data.text || '');
1541
+ var qLines = qText.split('\n');
1542
+ var quoteParts = [];
1543
+ for (var qi = 0; qi < qLines.length; qi++) {
1544
+ quoteParts.push('> ' + qLines[qi]);
1545
+ }
1546
+ parts.push(quoteParts.join('\n'));
1547
+ break;
1548
+ case 'delimiter':
1549
+ parts.push('---');
1550
+ break;
1551
+ default:
1552
+ if (b.data && b.data.text) parts.push(inlineHtmlToMd(b.data.text));
1553
+ }
1554
+ }
1555
+ return parts.join('\n\n');
1556
+ }
784
1557
 
785
- var mw = document.getElementById('h-milestone-wrap');
786
- var am = D.milestones.find(function(m) { return m.status === 'active'; });
787
- if (am) {
788
- mw.style.display = '';
789
- document.getElementById('h-milestone-name').textContent = am.title || am.id || '';
790
- document.getElementById('h-milestone-progress').style.width = (am.progress || 0) + '%';
791
- } else { mw.style.display = 'none'; }
1558
+ /* ── Editor.js Instance Management ──────────────────────── */
1559
+ function initEditor(holderId, markdown, options) {
1560
+ if (typeof EditorJS === 'undefined') {
1561
+ var el = document.getElementById(holderId);
1562
+ if (el) {
1563
+ var ta = document.createElement('textarea');
1564
+ ta.value = markdown || '';
1565
+ ta.className = 'editor-fallback-textarea';
1566
+ ta.placeholder = (options && options.placeholder) || 'Type / for commands...';
1567
+ ta.id = holderId + '-fallback';
1568
+ el.appendChild(ta);
1569
+ }
1570
+ return { _fallback: true, _holderId: holderId };
1571
+ }
792
1572
 
793
- var sw = document.getElementById('h-sprint-wrap');
794
- var as = D.sprints.find(function(s) { return s.status === 'active'; });
795
- if (as) {
796
- sw.style.display = '';
797
- document.getElementById('h-sprint-name').textContent = as.goal || as.id || '';
798
- var dr = daysRemaining(as.end_date);
799
- document.getElementById('h-sprint-days').textContent = dr !== null ? (dr >= 0 ? dr + ' days left' : Math.abs(dr) + ' days over') : '';
800
- } else { sw.style.display = 'none'; }
1573
+ var opts = options || {};
1574
+ var tools = {};
801
1575
 
802
- var taskPlural = ENTITY_NAMES.task ? ENTITY_NAMES.task.plural : 'Tasks';
803
- var total = D.tasks.length;
804
- var done = D.tasks.filter(function(f) { return f.status === COMPLETED_STATUS; }).length;
805
- var inProgStatus = (D.config && D.config.statuses && D.config.statuses.task) ?
806
- ((D.config.statuses.task.find(function(s) { return s.icon === 'half-circle'; }) || {}).key || 'in-progress') : 'in-progress';
807
- var inProg = D.tasks.filter(function(f) { return f.status === inProgStatus; }).length;
808
- var vel = D.health && D.health.velocity != null ? D.health.velocity : (total ? Math.round(done / total * 100) : 0);
809
- document.getElementById('h-stats').innerHTML =
810
- '<div class="stat"><span class="stat-val">' + total + '</span><span class="stat-label">' + escHtml(taskPlural) + '</span></div>' +
811
- '<div class="stat"><span class="stat-val" style="color:var(--success)">' + done + '</span><span class="stat-label">Done</span></div>' +
812
- '<div class="stat"><span class="stat-val" style="color:var(--warning)">' + inProg + '</span><span class="stat-label">In Progress</span></div>' +
813
- '<div class="stat"><span class="stat-val" style="color:var(--accent)">' + vel + '%</span><span class="stat-label">Velocity</span></div>';
1576
+ if (typeof Header !== 'undefined') {
1577
+ tools.header = {
1578
+ class: Header,
1579
+ inlineToolbar: true,
1580
+ config: { levels: [1, 2, 3, 4], defaultLevel: 2 },
1581
+ };
1582
+ }
1583
+ if (typeof EditorjsList !== 'undefined') tools.list = { class: EditorjsList, inlineToolbar: true, config: { maxLevel: 3 } };
1584
+ if (typeof CodeTool !== 'undefined') tools.code = { class: CodeTool };
1585
+ if (typeof Quote !== 'undefined') tools.quote = { class: Quote, inlineToolbar: true, config: { quotePlaceholder: 'Write a quote...', captionPlaceholder: '' } };
1586
+ if (typeof Delimiter !== 'undefined') tools.delimiter = { class: Delimiter };
1587
+ if (typeof Marker !== 'undefined') tools.marker = { class: Marker };
1588
+ if (typeof InlineCode !== 'undefined') tools.inlineCode = { class: InlineCode };
1589
+
1590
+ var blocks = markdownToBlocks(markdown || '');
1591
+
1592
+ var editor = new EditorJS({
1593
+ holder: holderId,
1594
+ tools: tools,
1595
+ data: { blocks: blocks },
1596
+ placeholder: opts.placeholder || 'Type / for commands...',
1597
+ minHeight: opts.minHeight || 0,
1598
+ autofocus: opts.autofocus || false,
1599
+ onChange: opts.onChange || function() {},
1600
+ defaultBlock: 'paragraph',
1601
+ inlineToolbar: ['bold', 'italic', 'link', 'marker', 'inlineCode'],
1602
+ });
1603
+
1604
+ return editor;
1605
+ }
1606
+
1607
+ async function getEditorMarkdown(editorInstance) {
1608
+ if (!editorInstance) return '';
1609
+ if (editorInstance._fallback) {
1610
+ var ta = document.getElementById(editorInstance._holderId + '-fallback');
1611
+ return ta ? ta.value : '';
1612
+ }
1613
+ try {
1614
+ var data = await editorInstance.save();
1615
+ return blocksToMarkdown(data.blocks);
1616
+ } catch (e) {
1617
+ return '';
1618
+ }
1619
+ }
1620
+
1621
+ function destroyEditor(editorInstance) {
1622
+ if (!editorInstance) return;
1623
+ if (editorInstance._fallback) return;
1624
+ if (typeof editorInstance.destroy === 'function') {
1625
+ try { editorInstance.destroy(); } catch (e) { /* ignore */ }
1626
+ }
1627
+ }
1628
+
1629
+ /* ── Simple Markdown → HTML Renderer (readonly mode) ──── */
1630
+ function simpleMarkdownRender(md) {
1631
+ if (!md) return '';
1632
+ var lines = md.split('\n');
1633
+ var html = '';
1634
+ var inCode = false;
1635
+ var inList = false;
1636
+ var listType = '';
1637
+
1638
+ for (var i = 0; i < lines.length; i++) {
1639
+ var line = lines[i];
1640
+
1641
+ if (line.trim().startsWith('```')) {
1642
+ if (inCode) { html += '</code></pre>'; inCode = false; }
1643
+ else { html += '<pre><code>'; inCode = true; }
1644
+ continue;
1645
+ }
1646
+ if (inCode) { html += escHtml(line) + '\n'; continue; }
1647
+
1648
+ if (inList && !/^\s*[-*]\s/.test(line) && !/^\s*\d+\.\s/.test(line)) {
1649
+ html += '</' + listType + '>'; inList = false;
1650
+ }
1651
+
1652
+ if (line.trim() === '') { if (!inList) html += '<br>'; continue; }
1653
+ if (/^---\s*$/.test(line.trim())) { html += '<hr>'; continue; }
1654
+
1655
+ var hm = line.match(/^(#{1,6})\s+(.+)$/);
1656
+ if (hm) { html += '<h' + hm[1].length + '>' + inlineMdToHtml(hm[2]) + '</h' + hm[1].length + '>'; continue; }
1657
+
1658
+ if (/^>\s/.test(line)) { html += '<blockquote>' + inlineMdToHtml(line.substring(2)) + '</blockquote>'; continue; }
1659
+
1660
+ if (/^\s*-\s+\[[ x]\]\s/.test(line)) {
1661
+ var chm = line.match(/^\s*-\s+\[([ x])\]\s+(.+)$/);
1662
+ if (chm) {
1663
+ var checked = chm[1] === 'x';
1664
+ html += '<div class="md-check"><span class="md-check-box' + (checked ? ' checked' : '') + '">' + (checked ? '&#10003;' : '') + '</span> ' + inlineMdToHtml(chm[2]) + '</div>';
1665
+ }
1666
+ continue;
1667
+ }
1668
+
1669
+ if (/^\s*[-*]\s+/.test(line)) {
1670
+ if (!inList || listType !== 'ul') { if (inList) html += '</' + listType + '>'; html += '<ul>'; inList = true; listType = 'ul'; }
1671
+ var lmatch = line.match(/^\s*[-*]\s+(.+)$/);
1672
+ if (lmatch) html += '<li>' + inlineMdToHtml(lmatch[1]) + '</li>';
1673
+ continue;
1674
+ }
1675
+ if (/^\s*\d+\.\s+/.test(line)) {
1676
+ if (!inList || listType !== 'ol') { if (inList) html += '</' + listType + '>'; html += '<ol>'; inList = true; listType = 'ol'; }
1677
+ var omatch = line.match(/^\s*\d+\.\s+(.+)$/);
1678
+ if (omatch) html += '<li>' + inlineMdToHtml(omatch[1]) + '</li>';
1679
+ continue;
1680
+ }
1681
+
1682
+ html += '<p>' + inlineMdToHtml(line) + '</p>';
1683
+ }
1684
+
1685
+ if (inCode) html += '</code></pre>';
1686
+ if (inList) html += '</' + listType + '>';
1687
+ return html;
814
1688
  }
815
1689
 
1690
+
816
1691
  /* ══════════════════════════════════════════════════════════════
817
1692
  BOARD VIEW
818
1693
  ══════════════════════════════════════════════════════════════ */
@@ -918,6 +1793,7 @@ function renderCard(f) {
918
1793
  '<div class="card-meta">' +
919
1794
  (f.points != null ? '<span class="pill pill-points">' + f.points + ' pts</span>' : '') +
920
1795
  (f.epic ? '<span class="pill pill-epic" style="background:' + ec + '">' + escHtml(f.epic) + '</span>' : '') +
1796
+ (f.ai && Object.keys(f.ai).length > 0 ? '<span class="pill pill-ai" title="AI properties">&#9889;</span>' : '') +
921
1797
  (assigned ? '<span class="card-assigned">' + escHtml(assigned) + '</span>' : '') +
922
1798
  '</div></div>';
923
1799
  }
@@ -1199,51 +2075,6 @@ function renderMetrics() {
1199
2075
  '<div class="metric-card"><h3>Project Health</h3>' + qualHtml + '</div></div>';
1200
2076
  }
1201
2077
 
1202
- /* ══════════════════════════════════════════════════════════════
1203
- CRUD HELPERS
1204
- ══════════════════════════════════════════════════════════════ */
1205
- function isSourceWritable() {
1206
- if (!hasWorkspace() || !D.activeSource) return true; // legacy mode = writable
1207
- var src = D.config.workspace.sources.find(function(s) { return s.name === D.activeSource; });
1208
- return src ? !src.readonly : true;
1209
- }
1210
-
1211
- function openCreateDialog(collection) {
1212
- var item = { _isNew: true };
1213
-
1214
- if (collection === 'tasks') {
1215
- // Pre-fill milestone and epic from available ones
1216
- var ms = D.milestones.length > 0 ? (D.milestones[0].id || D.milestones[0]._dir || '') : '';
1217
- var ep = D.epics.length > 0 ? (D.epics[0]._dir || D.epics[0].id || '') : '';
1218
- item.title = '';
1219
- item.status = 'backlog';
1220
- item.priority = '';
1221
- item.points = null;
1222
- item.assigned = '';
1223
- item.sprint = '';
1224
- item.milestone = ms;
1225
- item.epic = ep;
1226
- item.content = '';
1227
- } else if (collection === 'milestones') {
1228
- item.title = '';
1229
- item.status = 'planned';
1230
- item.deadline = '';
1231
- item.content = '';
1232
- } else if (collection === 'epics') {
1233
- var ms = D.milestones.length > 0 ? (D.milestones[0]._dir || D.milestones[0].id || '') : '';
1234
- item.title = '';
1235
- item.status = 'active';
1236
- item.priority = '';
1237
- item.milestone = ms;
1238
- item.content = '';
1239
- }
1240
-
1241
- panelState = { open: true, type: collection, item: item, isCreate: true };
1242
- renderPanel();
1243
- document.getElementById('detail-panel').classList.add('open');
1244
- document.getElementById('panel-overlay').classList.add('open');
1245
- }
1246
-
1247
2078
  /* ══════════════════════════════════════════════════════════════
1248
2079
  OVERVIEW VIEW
1249
2080
  ══════════════════════════════════════════════════════════════ */
@@ -1350,70 +2181,304 @@ async function renderOverview() {
1350
2181
  }
1351
2182
 
1352
2183
  /* ══════════════════════════════════════════════════════════════
1353
- DETAIL PANELEdit/Create tasks, epics, milestones
2184
+ NOTES VIEWNotion-style split layout
1354
2185
  ══════════════════════════════════════════════════════════════ */
1355
- var panelState = { open: false, type: null, item: null, isCreate: false };
1356
2186
 
1357
- function openPanel(type, item) {
1358
- panelState = { open: true, type: type, item: JSON.parse(JSON.stringify(item)), isCreate: false };
1359
- renderPanel();
1360
- document.getElementById('detail-panel').classList.add('open');
1361
- document.getElementById('panel-overlay').classList.add('open');
2187
+ var notesState = { notes: [], activeId: null, editor: null, dirty: false };
2188
+
2189
+ function renderNotes() {
2190
+ var container = document.getElementById('notes-container');
2191
+ if (!container) return;
2192
+
2193
+ container.innerHTML =
2194
+ '<div class="notes-layout">' +
2195
+ '<div class="notes-sidebar">' +
2196
+ '<div class="notes-toolbar">' +
2197
+ '<input type="text" placeholder="Search..." id="notes-search">' +
2198
+ '<button class="btn btn-sm btn-create" id="notes-new">+ New</button>' +
2199
+ '</div>' +
2200
+ '<div class="notes-list" id="notes-list"></div>' +
2201
+ '</div>' +
2202
+ '<div class="notes-editor" id="notes-editor-pane">' +
2203
+ '<div class="notes-empty-state">' +
2204
+ '<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>' +
2205
+ '<p>Select a note or create a new one</p>' +
2206
+ '<p style="font-size:12px;margin-top:4px;color:var(--text3)">Use <kbd>/</kbd> for block commands</p>' +
2207
+ '</div>' +
2208
+ '</div>' +
2209
+ '</div>';
2210
+
2211
+ loadNoteList();
2212
+
2213
+ document.getElementById('notes-new').addEventListener('click', createNewNote);
2214
+ document.getElementById('notes-search').addEventListener('input', function() {
2215
+ renderNoteList(this.value.toLowerCase());
2216
+ });
1362
2217
  }
1363
2218
 
1364
- function closePanel() {
1365
- panelState = { open: false, type: null, item: null };
1366
- document.getElementById('detail-panel').classList.remove('open');
1367
- document.getElementById('panel-overlay').classList.remove('open');
2219
+ async function loadNoteList() {
2220
+ var base = apiBase();
2221
+ var notes = await fetchJson(base + '/notes');
2222
+ notesState.notes = notes || [];
2223
+ renderNoteList('');
1368
2224
  }
1369
2225
 
1370
- function renderPanel() {
1371
- var panel = document.getElementById('detail-panel');
1372
- var t = panelState.type;
1373
- var item = panelState.item;
1374
- var isCreate = panelState.isCreate;
1375
- if (!item) return;
2226
+ function renderNoteList(filter) {
2227
+ var list = document.getElementById('notes-list');
2228
+ if (!list) return;
1376
2229
 
1377
- var isReadonly = !isCreate && item.readonly;
2230
+ var notes = notesState.notes;
2231
+ if (filter) {
2232
+ notes = notes.filter(function(n) {
2233
+ return (n.title || '').toLowerCase().indexOf(filter) !== -1;
2234
+ });
2235
+ }
1378
2236
 
1379
- var typeLabel;
1380
- if (t === 'tasks') typeLabel = ENTITY_NAMES.task ? ENTITY_NAMES.task.singular : 'Task';
1381
- else if (t === 'epics') typeLabel = ENTITY_NAMES.epic ? ENTITY_NAMES.epic.singular : 'Epic';
1382
- else typeLabel = ENTITY_NAMES.milestone ? ENTITY_NAMES.milestone.singular : 'Milestone';
2237
+ notes.sort(function(a, b) {
2238
+ return (b.updated || b.created || '').localeCompare(a.updated || a.created || '');
2239
+ });
1383
2240
 
1384
- var html = '<div class="panel-header">' +
1385
- '<span class="panel-type">' + escHtml(isCreate ? 'New ' + typeLabel : typeLabel) + '</span>' +
1386
- '<span class="panel-item-id">' + escHtml(isCreate ? '' : (item.id || '')) + '</span>';
2241
+ if (notes.length === 0) {
2242
+ list.innerHTML = '<div class="notes-list-empty">No notes yet</div>';
2243
+ return;
2244
+ }
1387
2245
 
1388
- // Source badge
1389
- if (item.source && item.sourceColor) {
1390
- html += '<span class="pill" style="background:' + item.sourceColor + '20;color:' + item.sourceColor + ';font-size:10px">' + escHtml(item.sourceLabel || item.source) + '</span>';
2246
+ var html = '';
2247
+ for (var i = 0; i < notes.length; i++) {
2248
+ var n = notes[i];
2249
+ var active = n.id === notesState.activeId ? ' active' : '';
2250
+ html += '<div class="notes-list-item' + active + '" data-id="' + escHtml(n.id) + '">' +
2251
+ '<div class="notes-list-title">' + escHtml(n.title || 'Untitled') + '</div>' +
2252
+ '<div class="notes-list-date">' + escHtml(n.updated || n.created || '') + '</div>' +
2253
+ '</div>';
1391
2254
  }
2255
+ list.innerHTML = html;
1392
2256
 
1393
- html += '<button class="panel-close" id="panel-close-btn">&times;</button></div>';
2257
+ list.querySelectorAll('.notes-list-item').forEach(function(el) {
2258
+ el.addEventListener('click', function() {
2259
+ selectNote(el.dataset.id);
2260
+ });
2261
+ });
2262
+ }
1394
2263
 
1395
- html += '<div class="panel-body">';
2264
+ async function selectNote(id) {
2265
+ if (notesState.editor) {
2266
+ destroyEditor(notesState.editor);
2267
+ notesState.editor = null;
2268
+ }
1396
2269
 
1397
- // Title
1398
- html += '<div class="panel-field"><label>Title</label><input type="text" id="p-title" value="' + escHtml(item.title || '') + '"' + (isReadonly ? ' disabled' : '') + '></div>';
2270
+ notesState.activeId = id;
2271
+ renderNoteList(document.getElementById('notes-search') ? document.getElementById('notes-search').value.toLowerCase() : '');
1399
2272
 
1400
- // Status + Priority row
1401
- html += '<div class="panel-props">';
2273
+ var pane = document.getElementById('notes-editor-pane');
2274
+ if (!pane) return;
1402
2275
 
1403
- // Status derived from config
1404
- var statusOptions, statusLabels;
1405
- if (t === 'tasks' && D.config && D.config.statuses && D.config.statuses.task) {
1406
- statusOptions = D.config.statuses.task.map(function(s) { return s.key; });
1407
- statusLabels = {};
1408
- D.config.statuses.task.forEach(function(s) { statusLabels[s.key] = s.label; });
1409
- } else if (t === 'epics' && D.config && D.config.statuses && D.config.statuses.epic) {
1410
- statusOptions = D.config.statuses.epic.map(function(s) { return s.key; });
1411
- statusLabels = {};
1412
- D.config.statuses.epic.forEach(function(s) { statusLabels[s.key] = s.label; });
1413
- } else if (t === 'milestones' && D.config && D.config.statuses && D.config.statuses.milestone) {
1414
- statusOptions = D.config.statuses.milestone.map(function(s) { return s.key; });
1415
- statusLabels = {};
1416
- D.config.statuses.milestone.forEach(function(s) { statusLabels[s.key] = s.label; });
2276
+ pane.innerHTML = '<div class="notes-editor-loading">Loading...</div>';
2277
+
2278
+ var base = apiBase();
2279
+ var note = await fetchJson(base + '/notes/' + encodeURIComponent(id));
2280
+ if (!note) {
2281
+ pane.innerHTML = '<div class="notes-empty-state"><p>Note not found</p></div>';
2282
+ return;
2283
+ }
2284
+
2285
+ pane.innerHTML =
2286
+ '<div class="notes-header-bar">' +
2287
+ '<input type="text" class="notes-title-input" id="notes-title" placeholder="Untitled" value="' + escHtml(note.title || '') + '">' +
2288
+ '<span class="notes-meta">' + escHtml(note.updated || note.created || '') + '</span>' +
2289
+ '</div>' +
2290
+ '<div class="notes-editorjs-wrap" id="notes-editorjs"></div>' +
2291
+ '<div class="notes-editor-footer">' +
2292
+ '<button class="btn btn-danger btn-sm" id="notes-delete">Delete</button>' +
2293
+ '<span style="flex:1"></span>' +
2294
+ '<button class="btn btn-primary" id="notes-save">Save</button>' +
2295
+ '</div>';
2296
+
2297
+ notesState.editor = initEditor('notes-editorjs', note.content || '', {
2298
+ placeholder: 'Type / for commands...',
2299
+ autofocus: true,
2300
+ onChange: function() { notesState.dirty = true; }
2301
+ });
2302
+ notesState.dirty = false;
2303
+
2304
+ document.getElementById('notes-save').addEventListener('click', saveCurrentNote);
2305
+ document.getElementById('notes-delete').addEventListener('click', function() {
2306
+ deleteCurrentNote(id);
2307
+ });
2308
+
2309
+ // Ctrl/Cmd+S to save
2310
+ pane.addEventListener('keydown', function(e) {
2311
+ if ((e.metaKey || e.ctrlKey) && e.key === 's') {
2312
+ e.preventDefault();
2313
+ saveCurrentNote();
2314
+ }
2315
+ });
2316
+ }
2317
+
2318
+ async function saveCurrentNote() {
2319
+ if (!notesState.activeId) return;
2320
+
2321
+ var titleEl = document.getElementById('notes-title');
2322
+ var title = titleEl ? titleEl.value : '';
2323
+ var content = notesState.editor ? await getEditorMarkdown(notesState.editor) : '';
2324
+
2325
+ var updates = {};
2326
+ var current = notesState.notes.find(function(n) { return n.id === notesState.activeId; });
2327
+ if (title && (!current || title !== current.title)) updates.title = title;
2328
+ updates.content = content;
2329
+ updates.updated = new Date().toISOString().split('T')[0];
2330
+
2331
+ var ok = await patchItem('notes', notesState.activeId, updates);
2332
+ if (ok) {
2333
+ notesState.dirty = false;
2334
+ await loadNoteList();
2335
+ }
2336
+ }
2337
+
2338
+ async function createNewNote() {
2339
+ var result = await createItem('notes', { title: 'Untitled', content: '' });
2340
+ if (result && result.id) {
2341
+ await loadNoteList();
2342
+ selectNote(result.id);
2343
+ }
2344
+ }
2345
+
2346
+ async function deleteCurrentNote(id) {
2347
+ if (!confirm('Delete this note?')) return;
2348
+ var ok = await deleteItem('notes', id);
2349
+ if (ok) {
2350
+ if (notesState.editor) {
2351
+ destroyEditor(notesState.editor);
2352
+ notesState.editor = null;
2353
+ }
2354
+ notesState.activeId = null;
2355
+ await loadNoteList();
2356
+ var pane = document.getElementById('notes-editor-pane');
2357
+ if (pane) {
2358
+ pane.innerHTML =
2359
+ '<div class="notes-empty-state">' +
2360
+ '<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>' +
2361
+ '<p>Select a note or create a new one</p>' +
2362
+ '</div>';
2363
+ }
2364
+ }
2365
+ }
2366
+
2367
+
2368
+ /* ══════════════════════════════════════════════════════════════
2369
+ CRUD HELPERS
2370
+ ══════════════════════════════════════════════════════════════ */
2371
+ function isSourceWritable() {
2372
+ if (!hasWorkspace() || !D.activeSource) return true;
2373
+ var src = D.config.workspace.sources.find(function(s) { return s.name === D.activeSource; });
2374
+ return src ? !src.readonly : true;
2375
+ }
2376
+
2377
+ function openCreateDialog(collection) {
2378
+ var item = { _isNew: true };
2379
+
2380
+ if (collection === 'tasks') {
2381
+ var ms = D.milestones.length > 0 ? (D.milestones[0].id || D.milestones[0]._dir || '') : '';
2382
+ var ep = D.epics.length > 0 ? (D.epics[0]._dir || D.epics[0].id || '') : '';
2383
+ item.title = '';
2384
+ item.status = 'backlog';
2385
+ item.priority = '';
2386
+ item.points = null;
2387
+ item.assigned = '';
2388
+ item.sprint = '';
2389
+ item.milestone = ms;
2390
+ item.epic = ep;
2391
+ item.content = '';
2392
+ } else if (collection === 'milestones') {
2393
+ item.title = '';
2394
+ item.status = 'planned';
2395
+ item.deadline = '';
2396
+ item.content = '';
2397
+ } else if (collection === 'epics') {
2398
+ var ms = D.milestones.length > 0 ? (D.milestones[0]._dir || D.milestones[0].id || '') : '';
2399
+ item.title = '';
2400
+ item.status = 'active';
2401
+ item.priority = '';
2402
+ item.milestone = ms;
2403
+ item.content = '';
2404
+ }
2405
+
2406
+ panelState = { open: true, type: collection, item: item, isCreate: true, editor: null };
2407
+ renderPanel();
2408
+ document.getElementById('detail-panel').classList.add('open');
2409
+ document.getElementById('panel-overlay').classList.add('open');
2410
+ }
2411
+
2412
+ /* ══════════════════════════════════════════════════════════════
2413
+ DETAIL PANEL — Notion-style: form top + editor bottom
2414
+ ══════════════════════════════════════════════════════════════ */
2415
+ var panelState = { open: false, type: null, item: null, isCreate: false, editor: null };
2416
+
2417
+ function openPanel(type, item) {
2418
+ panelState = { open: true, type: type, item: JSON.parse(JSON.stringify(item)), isCreate: false, editor: null };
2419
+ renderPanel();
2420
+ document.getElementById('detail-panel').classList.add('open');
2421
+ document.getElementById('panel-overlay').classList.add('open');
2422
+ }
2423
+
2424
+ function closePanel() {
2425
+ if (panelState.editor) { destroyEditor(panelState.editor); panelState.editor = null; }
2426
+ panelState = { open: false, type: null, item: null, editor: null };
2427
+ document.getElementById('detail-panel').classList.remove('open');
2428
+ document.getElementById('panel-overlay').classList.remove('open');
2429
+ }
2430
+
2431
+ function renderPanel() {
2432
+ var panel = document.getElementById('detail-panel');
2433
+ var t = panelState.type;
2434
+ var item = panelState.item;
2435
+ var isCreate = panelState.isCreate;
2436
+ if (!item) return;
2437
+
2438
+ var isReadonly = !isCreate && item.readonly;
2439
+
2440
+ var typeLabel;
2441
+ if (t === 'tasks') typeLabel = ENTITY_NAMES.task ? ENTITY_NAMES.task.singular : 'Task';
2442
+ else if (t === 'epics') typeLabel = ENTITY_NAMES.epic ? ENTITY_NAMES.epic.singular : 'Epic';
2443
+ else typeLabel = ENTITY_NAMES.milestone ? ENTITY_NAMES.milestone.singular : 'Milestone';
2444
+
2445
+ // ── Header ──
2446
+ var html = '<div class="panel-header">' +
2447
+ '<span class="panel-type">' + escHtml(isCreate ? 'New ' + typeLabel : typeLabel) + '</span>' +
2448
+ '<span class="panel-item-id">' + escHtml(isCreate ? '' : (item.id || '')) + '</span>';
2449
+
2450
+ if (item.source && item.sourceColor) {
2451
+ html += '<span class="pill" style="background:' + item.sourceColor + '20;color:' + item.sourceColor + ';font-size:10px">' + escHtml(item.sourceLabel || item.source) + '</span>';
2452
+ }
2453
+
2454
+ html += '<button class="panel-close" id="panel-close-btn">&times;</button></div>';
2455
+
2456
+ // ── Title (large, Notion-style) ──
2457
+ html += '<div class="panel-title-wrap">' +
2458
+ '<input type="text" id="p-title" class="panel-title-input" value="' + escHtml(item.title || '') + '" placeholder="Untitled"' + (isReadonly ? ' disabled' : '') + '>' +
2459
+ '</div>';
2460
+
2461
+ // ── Properties (compact inline) ──
2462
+ html += '<div class="panel-properties">';
2463
+
2464
+ // Properties toggle header
2465
+ html += '<div class="prop-row prop-row-toggle" id="props-toggle"><span class="prop-label" style="font-weight:600"><span class="ai-toggle-arrow" id="props-arrow">&#9654;</span> Properties</span><div class="prop-value"></div></div>';
2466
+ html += '<div class="props-fields-body" id="props-fields-body" style="display:none">';
2467
+
2468
+ // Status
2469
+ var statusOptions, statusLabels;
2470
+ if (t === 'tasks' && D.config && D.config.statuses && D.config.statuses.task) {
2471
+ statusOptions = D.config.statuses.task.map(function(s) { return s.key; });
2472
+ statusLabels = {};
2473
+ D.config.statuses.task.forEach(function(s) { statusLabels[s.key] = s.label; });
2474
+ } else if (t === 'epics' && D.config && D.config.statuses && D.config.statuses.epic) {
2475
+ statusOptions = D.config.statuses.epic.map(function(s) { return s.key; });
2476
+ statusLabels = {};
2477
+ D.config.statuses.epic.forEach(function(s) { statusLabels[s.key] = s.label; });
2478
+ } else if (t === 'milestones' && D.config && D.config.statuses && D.config.statuses.milestone) {
2479
+ statusOptions = D.config.statuses.milestone.map(function(s) { return s.key; });
2480
+ statusLabels = {};
2481
+ D.config.statuses.milestone.forEach(function(s) { statusLabels[s.key] = s.label; });
1417
2482
  } else if (t === 'tasks') {
1418
2483
  statusOptions = STATUSES;
1419
2484
  statusLabels = STATUS_LABELS;
@@ -1422,116 +2487,175 @@ function renderPanel() {
1422
2487
  statusLabels = { planned: 'Planned', active: 'Active', completed: 'Completed', cancelled: 'Cancelled' };
1423
2488
  }
1424
2489
 
1425
- html += '<div class="panel-field"><label>Status</label><div class="field-with-icon"><span id="p-status-icon">' + statusIcon(item.status) + '</span>' +
2490
+ html += '<div class="prop-row"><span class="prop-label">Status</span><div class="prop-value"><span id="p-status-icon">' + statusIcon(item.status) + '</span>' +
1426
2491
  '<select id="p-status"' + (isReadonly ? ' disabled' : '') + '>' + statusOptions.map(function(s) {
1427
2492
  return '<option value="' + s + '"' + (item.status === s ? ' selected' : '') + '>' + (statusLabels[s] || s) + '</option>';
1428
2493
  }).join('') + '</select></div></div>';
1429
2494
 
1430
- // Priority
1431
2495
  if (t === 'tasks' || t === 'epics') {
1432
- html += '<div class="panel-field"><label>Priority</label><div class="field-with-icon"><span id="p-priority-icon">' + priorityIcon(item.priority) + '</span>' +
2496
+ html += '<div class="prop-row"><span class="prop-label">Priority</span><div class="prop-value"><span id="p-priority-icon">' + priorityIcon(item.priority) + '</span>' +
1433
2497
  '<select id="p-priority"' + (isReadonly ? ' disabled' : '') + '><option value="">None</option>' + PRIORITIES.map(function(p) {
1434
2498
  return '<option value="' + p + '"' + (item.priority === p ? ' selected' : '') + '>' + (PRIORITY_LABELS[p] || p) + '</option>';
1435
2499
  }).join('') + '</select></div></div>';
1436
2500
  }
1437
2501
 
1438
- html += '</div>'; // close panel-props
1439
-
1440
- // Type-specific fields
1441
2502
  if (t === 'tasks') {
1442
- html += '<div class="panel-props">';
1443
- html += '<div class="panel-field"><label>Points</label><input type="number" id="p-points" min="0" max="100" value="' + (item.points != null ? item.points : '') + '"' + (isReadonly ? ' disabled' : '') + '></div>';
2503
+ html += '<div class="prop-row"><span class="prop-label">Points</span><div class="prop-value"><input type="number" id="p-points" min="0" max="100" value="' + (item.points != null ? item.points : '') + '" placeholder="0"' + (isReadonly ? ' disabled' : '') + '></div></div>';
1444
2504
 
1445
2505
  var assignedVal = Array.isArray(item.assigned) ? item.assigned.join(', ') : (item.assigned || '');
1446
- html += '<div class="panel-field"><label>Assigned</label><input type="text" id="p-assigned" value="' + escHtml(assignedVal) + '" placeholder="agent-name"' + (isReadonly ? ' disabled' : '') + '></div>';
1447
- html += '</div>';
2506
+ html += '<div class="prop-row"><span class="prop-label">Assigned</span><div class="prop-value"><input type="text" id="p-assigned" value="' + escHtml(assignedVal) + '" placeholder="Unassigned"' + (isReadonly ? ' disabled' : '') + '></div></div>';
1448
2507
 
1449
- html += '<div class="panel-props">';
1450
- // Sprint
1451
- html += '<div class="panel-field"><label>Sprint</label><select id="p-sprint"' + (isReadonly ? ' disabled' : '') + '><option value="">None</option>' +
2508
+ html += '<div class="prop-row"><span class="prop-label">Sprint</span><div class="prop-value"><select id="p-sprint"' + (isReadonly ? ' disabled' : '') + '><option value="">None</option>' +
1452
2509
  D.allSprints.map(function(s) { return '<option value="' + escHtml(s.id || '') + '"' + (item.sprint === s.id ? ' selected' : '') + '>' + escHtml(s.id || '') + '</option>'; }).join('') +
1453
- '</select></div>';
2510
+ '</select></div></div>';
1454
2511
 
1455
2512
  if (isCreate) {
1456
- // Milestone and Epic as editable dropdowns for create mode
1457
- html += '<div class="panel-field"><label>Milestone</label><select id="p-milestone">' +
2513
+ html += '<div class="prop-row"><span class="prop-label">Milestone</span><div class="prop-value"><select id="p-milestone">' +
1458
2514
  D.milestones.map(function(m) { return '<option value="' + escHtml(m._dir || m.id || '') + '"' + (item.milestone === (m._dir || m.id) ? ' selected' : '') + '>' + escHtml(m.title || m.id || '') + '</option>'; }).join('') +
1459
- '</select></div>';
1460
- html += '</div>';
1461
- html += '<div class="panel-field"><label>Epic</label><select id="p-epic-select">' +
2515
+ '</select></div></div>';
2516
+ html += '<div class="prop-row"><span class="prop-label">Epic</span><div class="prop-value"><select id="p-epic-select">' +
1462
2517
  D.epics.map(function(e) { return '<option value="' + escHtml(e._dir || e.id || '') + '"' + (item.epic === (e._dir || e.id) ? ' selected' : '') + '>' + escHtml(e.title || e.id || '') + '</option>'; }).join('') +
1463
- '</select></div>';
2518
+ '</select></div></div>';
1464
2519
  } else {
1465
- // Epic (read-only info)
1466
- html += '<div class="panel-field"><label>Epic</label><input type="text" id="p-epic" value="' + escHtml(item.epic || '') + '" disabled style="opacity:.6" title="Epic is determined by file location"></div>';
1467
- html += '</div>';
1468
- }
1469
-
1470
- // Links rendering
1471
- if (!isCreate && item.links && Array.isArray(item.links) && item.links.length > 0) {
1472
- html += '<div class="panel-field"><label>Links</label><div class="link-chips">';
1473
- item.links.forEach(function(link) {
1474
- var parts = String(link).split(':');
1475
- var srcName = parts.length === 2 ? parts[0] : null;
1476
- var srcColor = 'var(--accent)';
1477
- if (srcName && D.config && D.config.workspace && D.config.workspace.sources) {
1478
- var src = D.config.workspace.sources.find(function(s) { return s.name === srcName; });
1479
- if (src && src.color) srcColor = src.color;
1480
- }
1481
- html += '<span class="link-chip" data-link="' + escHtml(String(link)) + '">' +
1482
- '<span class="link-chip-dot" style="background:' + srcColor + '"></span>' +
1483
- escHtml(String(link)) + '</span>';
1484
- });
1485
- html += '</div></div>';
1486
- }
1487
-
1488
- // Reverse links
1489
- if (!isCreate && D.overviewLinks && D.overviewLinks.reverseLinks && item.id) {
1490
- var reverseRefs = D.overviewLinks.reverseLinks[item.id] || [];
1491
- if (reverseRefs.length > 0) {
1492
- html += '<div class="panel-field"><label>Referenced By</label><div class="link-chips">';
1493
- reverseRefs.forEach(function(ref) {
1494
- var srcColor = ref.sourceColor || 'var(--accent)';
1495
- html += '<span class="link-chip" data-link="' + escHtml(ref.from || '') + '">' +
1496
- '<span class="link-chip-dot" style="background:' + srcColor + '"></span>' +
1497
- escHtml(ref.from || '') + '</span>';
1498
- });
1499
- html += '</div></div>';
1500
- }
2520
+ html += '<div class="prop-row"><span class="prop-label">Epic</span><div class="prop-value"><span class="prop-text">' + escHtml(item.epic || 'None') + '</span></div></div>';
1501
2521
  }
1502
2522
  }
1503
2523
 
1504
2524
  if (t === 'milestones') {
1505
- html += '<div class="panel-field"><label>Deadline</label><input type="date" id="p-deadline" value="' + fmtDate(item.deadline) + '"' + (isReadonly ? ' disabled' : '') + '></div>';
2525
+ html += '<div class="prop-row"><span class="prop-label">Deadline</span><div class="prop-value"><input type="date" id="p-deadline" value="' + fmtDate(item.deadline) + '"' + (isReadonly ? ' disabled' : '') + '></div></div>';
1506
2526
  }
1507
2527
 
1508
2528
  if (isCreate && (t === 'epics' || t === 'milestones')) {
1509
2529
  if (t === 'epics') {
1510
- html += '<div class="panel-field"><label>Milestone</label><select id="p-milestone">' +
2530
+ html += '<div class="prop-row"><span class="prop-label">Milestone</span><div class="prop-value"><select id="p-milestone">' +
1511
2531
  D.milestones.map(function(m) { return '<option value="' + escHtml(m._dir || m.id || '') + '">' + escHtml(m.title || m.id || '') + '</option>'; }).join('') +
1512
- '</select></div>';
2532
+ '</select></div></div>';
2533
+ }
2534
+ }
2535
+
2536
+ // Links
2537
+ if (t === 'tasks' && !isCreate && item.links && Array.isArray(item.links) && item.links.length > 0) {
2538
+ html += '<div class="prop-row"><span class="prop-label">Links</span><div class="prop-value"><div class="link-chips">';
2539
+ item.links.forEach(function(link) {
2540
+ var parts = String(link).split(':');
2541
+ var srcName = parts.length === 2 ? parts[0] : null;
2542
+ var srcColor = 'var(--accent)';
2543
+ if (srcName && D.config && D.config.workspace && D.config.workspace.sources) {
2544
+ var src = D.config.workspace.sources.find(function(s) { return s.name === srcName; });
2545
+ if (src && src.color) srcColor = src.color;
2546
+ }
2547
+ html += '<span class="link-chip" data-link="' + escHtml(String(link)) + '">' +
2548
+ '<span class="link-chip-dot" style="background:' + srcColor + '"></span>' +
2549
+ escHtml(String(link)) + '</span>';
2550
+ });
2551
+ html += '</div></div></div>';
2552
+ }
2553
+
2554
+ // Reverse links
2555
+ if (t === 'tasks' && !isCreate && D.overviewLinks && D.overviewLinks.reverseLinks && item.id) {
2556
+ var reverseRefs = D.overviewLinks.reverseLinks[item.id] || [];
2557
+ if (reverseRefs.length > 0) {
2558
+ html += '<div class="prop-row"><span class="prop-label">Referenced By</span><div class="prop-value"><div class="link-chips">';
2559
+ reverseRefs.forEach(function(ref) {
2560
+ var srcColor = ref.sourceColor || 'var(--accent)';
2561
+ html += '<span class="link-chip" data-link="' + escHtml(ref.from || '') + '">' +
2562
+ '<span class="link-chip-dot" style="background:' + srcColor + '"></span>' +
2563
+ escHtml(ref.from || '') + '</span>';
2564
+ });
2565
+ html += '</div></div></div>';
1513
2566
  }
1514
2567
  }
1515
2568
 
1516
- // Description / Content
1517
- html += '<div class="panel-field"><label>Description</label><textarea id="p-content"' + (isReadonly ? ' disabled' : '') + '>' + escHtml(item.content || '') + '</textarea></div>';
2569
+ html += '</div>'; // close props-fields-body
2570
+
2571
+ // ── AI Properties — integrated as prop-rows ──
2572
+ var aiData = item.ai || {};
2573
+ var aiOwnData = item.aiOwn || {};
2574
+ var aiFieldDefs = [
2575
+ { key: 'skills', label: 'Skills', icon: '<svg class="icon icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>' },
2576
+ { key: 'agents', label: 'Agents', icon: '<svg class="icon icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="10" rx="2"/><circle cx="12" cy="5" r="3"/><circle cx="9" cy="16" r="1" fill="currentColor"/><circle cx="15" cy="16" r="1" fill="currentColor"/></svg>' },
2577
+ { key: 'mcps', label: 'MCPs', icon: '<svg class="icon icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16v16H4z"/><path d="M9 9h6v6H9z"/><path d="M9 1v3M15 1v3M9 20v3M15 20v3M1 9h3M1 15h3M20 9h3M20 15h3"/></svg>' },
2578
+ { key: 'commands', label: 'Commands', icon: '<svg class="icon icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>' },
2579
+ { key: 'context', label: 'Context', icon: '<svg class="icon icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>' }
2580
+ ];
2581
+ var aiTotalCount = 0;
2582
+ for (var ai = 0; ai < aiFieldDefs.length; ai++) {
2583
+ var arr = aiData[aiFieldDefs[ai].key];
2584
+ if (arr) aiTotalCount += arr.length;
2585
+ }
2586
+ var showAiSection = aiTotalCount > 0 || !isReadonly;
2587
+ if (showAiSection) {
2588
+ // AI divider row — clickable toggle
2589
+ html += '<div class="prop-row prop-row-divider prop-row-toggle" id="ai-toggle"><span class="prop-label" style="font-weight:600"><span class="ai-toggle-arrow" id="ai-arrow">&#9654;</span> AI</span><div class="prop-value">';
2590
+ if (aiTotalCount > 0) html += '<span class="pill" style="font-size:10px;padding:1px 6px">' + aiTotalCount + '</span>';
2591
+ html += '</div></div>';
2592
+ html += '<div class="ai-fields-body" id="ai-fields-body" style="display:none">';
2593
+ for (var ag = 0; ag < aiFieldDefs.length; ag++) {
2594
+ var def = aiFieldDefs[ag];
2595
+ var resolved = aiData[def.key] || [];
2596
+ var own = aiOwnData[def.key] || [];
2597
+ var inherited = resolved.filter(function(v) { return own.indexOf(v) === -1; });
2598
+ if (isReadonly && resolved.length === 0) continue;
2599
+ html += '<div class="prop-row"><span class="prop-label">' + def.icon + ' ' + escHtml(def.label) + '</span><div class="prop-value">';
2600
+ if (isReadonly) {
2601
+ // Readonly: just show tags
2602
+ for (var ci = 0; ci < resolved.length; ci++) {
2603
+ var isOwn = own.indexOf(resolved[ci]) !== -1;
2604
+ html += '<span class="ai-tag' + (isOwn ? ' ai-tag-own' : ' ai-tag-inherited') + '">' + escHtml(resolved[ci]) + '</span>';
2605
+ }
2606
+ } else {
2607
+ // Editable: tag input container
2608
+ html += '<div class="ai-tag-input" data-ai-field="' + def.key + '">';
2609
+ // Inherited tags (dashed, no remove)
2610
+ for (var ii = 0; ii < inherited.length; ii++) {
2611
+ html += '<span class="ai-tag ai-tag-inherited">' + escHtml(inherited[ii]) + '</span>';
2612
+ }
2613
+ // Own tags (accent, with remove button)
2614
+ for (var oi = 0; oi < own.length; oi++) {
2615
+ html += '<span class="ai-tag ai-tag-own"><span class="ai-tag-val">' + escHtml(own[oi]) + '</span><button class="ai-tag-remove" type="button">&times;</button></span>';
2616
+ }
2617
+ // Text input for adding
2618
+ html += '<input type="text" class="ai-tag-text" placeholder="Add...">';
2619
+ // Autocomplete dropdown
2620
+ html += '<div class="ai-autocomplete"></div>';
2621
+ html += '</div>';
2622
+ }
2623
+ html += '</div></div>';
2624
+ }
2625
+ html += '</div>'; // close ai-fields-body
2626
+ }
1518
2627
 
1519
- html += '</div>'; // close panel-body
2628
+ html += '</div>'; // close panel-properties
1520
2629
 
1521
- // Footer with buttons
2630
+ // ── Content Editor (fills remaining space) ──
2631
+ if (isReadonly) {
2632
+ html += '<div class="panel-content-area"><div id="p-content-editor" class="panel-editor-zone md-rendered">' + simpleMarkdownRender(item.content || '') + '</div></div>';
2633
+ } else {
2634
+ html += '<div class="panel-content-area"><div id="p-content-editor" class="panel-editor-zone"></div></div>';
2635
+ }
2636
+
2637
+ // ── Footer ──
1522
2638
  html += '<div class="panel-footer">';
1523
2639
  if (!isCreate && !isReadonly && isSourceWritable()) {
1524
- html += '<button class="btn btn-danger" id="panel-archive-btn">Archive</button>';
2640
+ html += '<button class="btn btn-danger btn-sm" id="panel-archive-btn">Archive</button>';
1525
2641
  }
1526
2642
  html += '<span style="flex:1"></span>';
1527
2643
  html += '<button class="btn" id="panel-cancel-btn">Cancel</button>';
1528
2644
  if (!isReadonly) {
1529
- html += '<button class="btn btn-primary" id="panel-save-btn">' + (isCreate ? 'Create' : 'Save Changes') + '</button>';
2645
+ html += '<button class="btn btn-primary" id="panel-save-btn">' + (isCreate ? 'Create' : 'Save') + '</button>';
1530
2646
  }
1531
2647
  html += '</div>';
1532
2648
 
1533
2649
  panel.innerHTML = html;
1534
2650
 
2651
+ // Initialize Editor.js for content (if not readonly)
2652
+ if (!isReadonly) {
2653
+ panelState.editor = initEditor('p-content-editor', item.content || '', {
2654
+ placeholder: 'Type / for commands, or start writing...',
2655
+ minHeight: 0,
2656
+ });
2657
+ }
2658
+
1535
2659
  // Event listeners
1536
2660
  document.getElementById('panel-close-btn').addEventListener('click', closePanel);
1537
2661
  document.getElementById('panel-cancel-btn').addEventListener('click', closePanel);
@@ -1541,13 +2665,145 @@ function renderPanel() {
1541
2665
  var archiveBtn = document.getElementById('panel-archive-btn');
1542
2666
  if (archiveBtn) {
1543
2667
  archiveBtn.addEventListener('click', function() {
1544
- if (!confirm('Archive this ' + typeLabel.toLowerCase() + '? It will be moved to the archive directory.')) return;
2668
+ if (!confirm('Archive this ' + typeLabel.toLowerCase() + '?')) return;
1545
2669
  deleteItem(t, item.id).then(function(ok) {
1546
2670
  if (ok) { closePanel(); refreshData(); }
1547
2671
  });
1548
2672
  });
1549
2673
  }
1550
2674
 
2675
+ // Properties section toggle
2676
+ var propsToggle = document.getElementById('props-toggle');
2677
+ if (propsToggle) {
2678
+ propsToggle.addEventListener('click', function() {
2679
+ var body = document.getElementById('props-fields-body');
2680
+ var arrow = document.getElementById('props-arrow');
2681
+ if (body.style.display === 'none') {
2682
+ body.style.display = '';
2683
+ arrow.innerHTML = '&#9660;';
2684
+ } else {
2685
+ body.style.display = 'none';
2686
+ arrow.innerHTML = '&#9654;';
2687
+ }
2688
+ });
2689
+ }
2690
+
2691
+ // AI section toggle
2692
+ var aiToggle = document.getElementById('ai-toggle');
2693
+ if (aiToggle) {
2694
+ aiToggle.addEventListener('click', function() {
2695
+ var body = document.getElementById('ai-fields-body');
2696
+ var arrow = document.getElementById('ai-arrow');
2697
+ if (body.style.display === 'none') {
2698
+ body.style.display = '';
2699
+ arrow.innerHTML = '&#9660;';
2700
+ } else {
2701
+ body.style.display = 'none';
2702
+ arrow.innerHTML = '&#9654;';
2703
+ }
2704
+ });
2705
+ }
2706
+
2707
+ // AI tag input event listeners
2708
+ panel.querySelectorAll('.ai-tag-input').forEach(function(container) {
2709
+ var field = container.dataset.aiField;
2710
+ var textInput = container.querySelector('.ai-tag-text');
2711
+ var dropdown = container.querySelector('.ai-autocomplete');
2712
+ if (!textInput || !dropdown) return;
2713
+
2714
+ function getOwnValues() {
2715
+ var vals = [];
2716
+ container.querySelectorAll('.ai-tag-own .ai-tag-val').forEach(function(el) {
2717
+ vals.push(el.textContent);
2718
+ });
2719
+ return vals;
2720
+ }
2721
+
2722
+ function getAllValues() {
2723
+ var vals = [];
2724
+ container.querySelectorAll('.ai-tag').forEach(function(el) {
2725
+ var valEl = el.querySelector('.ai-tag-val');
2726
+ vals.push(valEl ? valEl.textContent : el.textContent);
2727
+ });
2728
+ return vals;
2729
+ }
2730
+
2731
+ function addTag(value) {
2732
+ var v = value.trim();
2733
+ if (!v) return;
2734
+ var existing = getAllValues();
2735
+ if (existing.indexOf(v) !== -1) return;
2736
+ var tag = document.createElement('span');
2737
+ tag.className = 'ai-tag ai-tag-own';
2738
+ tag.innerHTML = '<span class="ai-tag-val">' + escHtml(v) + '</span><button class="ai-tag-remove" type="button">&times;</button>';
2739
+ tag.querySelector('.ai-tag-remove').addEventListener('click', function() { tag.remove(); });
2740
+ container.insertBefore(tag, textInput);
2741
+ textInput.value = '';
2742
+ closeDropdown();
2743
+ }
2744
+
2745
+ function closeDropdown() {
2746
+ dropdown.classList.remove('open');
2747
+ dropdown.innerHTML = '';
2748
+ }
2749
+
2750
+ function showDropdown() {
2751
+ var suggestions = D.aiSuggestions ? (D.aiSuggestions[field] || []) : [];
2752
+ var present = getAllValues();
2753
+ var query = textInput.value.trim().toLowerCase();
2754
+ var filtered = suggestions.filter(function(s) {
2755
+ return present.indexOf(s) === -1 && (!query || s.toLowerCase().indexOf(query) !== -1);
2756
+ });
2757
+ if (filtered.length === 0) { closeDropdown(); return; }
2758
+ dropdown.innerHTML = '';
2759
+ filtered.forEach(function(s) {
2760
+ var item = document.createElement('div');
2761
+ item.className = 'ai-autocomplete-item';
2762
+ item.textContent = s;
2763
+ item.addEventListener('mousedown', function(e) {
2764
+ e.preventDefault();
2765
+ addTag(s);
2766
+ });
2767
+ dropdown.appendChild(item);
2768
+ });
2769
+ dropdown.classList.add('open');
2770
+ }
2771
+
2772
+ // Click on × to remove own tag
2773
+ container.querySelectorAll('.ai-tag-remove').forEach(function(btn) {
2774
+ btn.addEventListener('click', function() { btn.closest('.ai-tag').remove(); });
2775
+ });
2776
+
2777
+ // Click on container focuses input
2778
+ container.addEventListener('click', function(e) {
2779
+ if (e.target === container) textInput.focus();
2780
+ });
2781
+
2782
+ // Input typing → show autocomplete
2783
+ textInput.addEventListener('input', showDropdown);
2784
+ textInput.addEventListener('focus', showDropdown);
2785
+
2786
+ // Enter → add tag
2787
+ textInput.addEventListener('keydown', function(e) {
2788
+ if (e.key === 'Enter') {
2789
+ e.preventDefault();
2790
+ if (textInput.value.trim()) addTag(textInput.value);
2791
+ } else if (e.key === 'Backspace' && textInput.value === '') {
2792
+ // Remove last own tag
2793
+ var ownTags = container.querySelectorAll('.ai-tag-own');
2794
+ if (ownTags.length > 0) ownTags[ownTags.length - 1].remove();
2795
+ } else if (e.key === 'Escape') {
2796
+ closeDropdown();
2797
+ textInput.blur();
2798
+ }
2799
+ });
2800
+
2801
+ // Close dropdown on blur
2802
+ textInput.addEventListener('blur', function() {
2803
+ setTimeout(closeDropdown, 150);
2804
+ });
2805
+ });
2806
+
1551
2807
  // Live icon updates
1552
2808
  var statusSel = document.getElementById('p-status');
1553
2809
  if (statusSel) {
@@ -1564,6 +2820,15 @@ function renderPanel() {
1564
2820
  });
1565
2821
  }
1566
2822
 
2823
+ // Ctrl/Cmd+S to save
2824
+ panel.addEventListener('keydown', function(e) {
2825
+ if ((e.metaKey || e.ctrlKey) && e.key === 's') {
2826
+ e.preventDefault();
2827
+ if (isCreate) saveCreatePanel();
2828
+ else savePanel();
2829
+ }
2830
+ });
2831
+
1567
2832
  // Link chip click handlers
1568
2833
  panel.querySelectorAll('.link-chip[data-link]').forEach(function(chip) {
1569
2834
  chip.addEventListener('click', function() {
@@ -1622,8 +2887,30 @@ async function savePanel() {
1622
2887
  if (deadlineEl && deadlineEl.value !== fmtDate(item.deadline)) updates.deadline = deadlineEl.value || null;
1623
2888
  }
1624
2889
 
1625
- var contentEl = document.getElementById('p-content');
1626
- if (contentEl && contentEl.value !== (item.content || '')) updates.content = contentEl.value;
2890
+ if (panelState.editor) {
2891
+ var newContent = await getEditorMarkdown(panelState.editor);
2892
+ if (newContent !== (item.content || '')) updates.content = newContent;
2893
+ }
2894
+
2895
+ // AI properties — read from tag inputs
2896
+ var aiTagInputs = document.querySelectorAll('.ai-tag-input[data-ai-field]');
2897
+ if (aiTagInputs.length > 0) {
2898
+ var aiObj = {};
2899
+ aiTagInputs.forEach(function(container) {
2900
+ var field = container.dataset.aiField;
2901
+ var vals = [];
2902
+ container.querySelectorAll('.ai-tag-own .ai-tag-val').forEach(function(el) {
2903
+ var v = el.textContent.trim();
2904
+ if (v) vals.push(v);
2905
+ });
2906
+ if (vals.length > 0) aiObj[field] = vals;
2907
+ });
2908
+ if (Object.keys(aiObj).length > 0) {
2909
+ updates.ai = aiObj;
2910
+ } else if (item.aiOwn && Object.keys(item.aiOwn).length > 0) {
2911
+ updates.ai = null;
2912
+ }
2913
+ }
1627
2914
 
1628
2915
  if (Object.keys(updates).length === 0) {
1629
2916
  showToast('No changes to save', 'success');
@@ -1682,8 +2969,26 @@ async function saveCreatePanel() {
1682
2969
  if (deadlineEl && deadlineEl.value) data.deadline = deadlineEl.value;
1683
2970
  }
1684
2971
 
1685
- var contentEl = document.getElementById('p-content');
1686
- if (contentEl && contentEl.value) data.content = contentEl.value;
2972
+ if (panelState.editor) {
2973
+ var editorContent = await getEditorMarkdown(panelState.editor);
2974
+ if (editorContent) data.content = editorContent;
2975
+ }
2976
+
2977
+ // AI properties — read from tag inputs
2978
+ var aiTagInputs = document.querySelectorAll('.ai-tag-input[data-ai-field]');
2979
+ if (aiTagInputs.length > 0) {
2980
+ var aiObj = {};
2981
+ aiTagInputs.forEach(function(container) {
2982
+ var field = container.dataset.aiField;
2983
+ var vals = [];
2984
+ container.querySelectorAll('.ai-tag-own .ai-tag-val').forEach(function(el) {
2985
+ var v = el.textContent.trim();
2986
+ if (v) vals.push(v);
2987
+ });
2988
+ if (vals.length > 0) aiObj[field] = vals;
2989
+ });
2990
+ if (Object.keys(aiObj).length > 0) data.ai = aiObj;
2991
+ }
1687
2992
 
1688
2993
  var result = await createItem(t, data);
1689
2994
  if (result) {
@@ -1698,6 +3003,192 @@ document.addEventListener('keydown', function(e) {
1698
3003
  if (e.key === 'Escape' && panelState.open) closePanel();
1699
3004
  });
1700
3005
 
3006
+
3007
+ /* ══════════════════════════════════════════════════════════════
3008
+ HISTORY — Project switch modal
3009
+ ══════════════════════════════════════════════════════════════ */
3010
+
3011
+ function openHistoryModal() {
3012
+ var overlay = document.getElementById('history-overlay');
3013
+ var modal = document.getElementById('history-modal');
3014
+ if (!overlay || !modal) return;
3015
+
3016
+ overlay.classList.add('open');
3017
+ modal.classList.add('open');
3018
+ 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>';
3019
+
3020
+ document.getElementById('history-close').onclick = closeHistoryModal;
3021
+ overlay.onclick = closeHistoryModal;
3022
+
3023
+ fetchJson('/api/history').then(function(entries) {
3024
+ renderHistoryList(entries || []);
3025
+ });
3026
+ }
3027
+
3028
+ function closeHistoryModal() {
3029
+ var overlay = document.getElementById('history-overlay');
3030
+ var modal = document.getElementById('history-modal');
3031
+ if (overlay) overlay.classList.remove('open');
3032
+ if (modal) modal.classList.remove('open');
3033
+ }
3034
+
3035
+ function renderHistoryList(entries) {
3036
+ var modal = document.getElementById('history-modal');
3037
+ if (!modal) return;
3038
+
3039
+ var body = modal.querySelector('.history-body');
3040
+ if (!body) return;
3041
+
3042
+ if (entries.length === 0) {
3043
+ body.innerHTML = '<div class="history-empty">No projects in history yet.<br>Open mdboard from a project directory to add it.</div>';
3044
+ return;
3045
+ }
3046
+
3047
+ // Sort: current first, then by lastOpened descending
3048
+ entries.sort(function(a, b) {
3049
+ if (a.isCurrent && !b.isCurrent) return -1;
3050
+ if (!a.isCurrent && b.isCurrent) return 1;
3051
+ return new Date(b.lastOpened) - new Date(a.lastOpened);
3052
+ });
3053
+
3054
+ var html = '';
3055
+ for (var i = 0; i < entries.length; i++) {
3056
+ var e = entries[i];
3057
+ var initial = (e.name || 'P').charAt(0).toUpperCase();
3058
+ var relTime = timeAgo(e.lastOpened);
3059
+ var currentClass = e.isCurrent ? ' history-item-current' : '';
3060
+ var currentBadge = e.isCurrent ? '<span class="history-current-badge">Current</span>' : '';
3061
+
3062
+ html += '<div class="history-item' + currentClass + '" data-path="' + escHtml(e.path) + '">' +
3063
+ '<div class="history-item-icon">' + escHtml(initial) + '</div>' +
3064
+ '<div class="history-item-info">' +
3065
+ '<div class="history-item-name">' + escHtml(e.name) + currentBadge + '</div>' +
3066
+ '<div class="history-item-path">' + escHtml(e.path) + '</div>' +
3067
+ '</div>' +
3068
+ '<div class="history-item-time">' + escHtml(relTime) + '</div>' +
3069
+ '</div>';
3070
+ }
3071
+
3072
+ body.innerHTML = html;
3073
+
3074
+ // Attach click handlers
3075
+ var items = body.querySelectorAll('.history-item');
3076
+ for (var j = 0; j < items.length; j++) {
3077
+ items[j].addEventListener('click', function() {
3078
+ var itemPath = this.getAttribute('data-path');
3079
+ if (this.classList.contains('history-item-current')) return;
3080
+ switchProject(itemPath);
3081
+ });
3082
+ }
3083
+ }
3084
+
3085
+ function switchProject(projectPath) {
3086
+ var body = document.querySelector('#history-modal .history-body');
3087
+ if (body) body.innerHTML = '<div class="history-loading">Switching project...</div>';
3088
+
3089
+ fetch('/api/history/switch', {
3090
+ method: 'POST',
3091
+ headers: { 'Content-Type': 'application/json' },
3092
+ body: JSON.stringify({ path: projectPath }),
3093
+ })
3094
+ .then(function(r) { return r.json(); })
3095
+ .then(function(data) {
3096
+ if (data && data.ok) {
3097
+ closeHistoryModal();
3098
+ // Reload everything — the SSE event will also trigger for other tabs
3099
+ D.activeSource = null;
3100
+ D.loaded = false;
3101
+ loadAll().then(function() {
3102
+ initFromConfig();
3103
+ if (hasWorkspace()) {
3104
+ buildSourceRail();
3105
+ D.activeSource = null;
3106
+ switchSource('overview');
3107
+ } else {
3108
+ renderAll();
3109
+ renderSidebarLogo();
3110
+ // Hide source rail if no workspace
3111
+ var rail = document.getElementById('source-rail');
3112
+ if (rail) rail.innerHTML = '';
3113
+ }
3114
+ });
3115
+ showToast('Switched to ' + (data.name || 'project'), 'success');
3116
+ } else {
3117
+ showToast('Error: ' + (data.error || 'Switch failed'), 'error');
3118
+ closeHistoryModal();
3119
+ }
3120
+ })
3121
+ .catch(function(err) {
3122
+ showToast('Error: ' + err.message, 'error');
3123
+ closeHistoryModal();
3124
+ });
3125
+ }
3126
+
3127
+ function checkAutoOpenHistory() {
3128
+ if (D.config && D.config.hasProject === false) {
3129
+ openHistoryModal();
3130
+ }
3131
+ }
3132
+
3133
+ function timeAgo(dateStr) {
3134
+ if (!dateStr) return '';
3135
+ var now = Date.now();
3136
+ var then = new Date(dateStr).getTime();
3137
+ var diff = Math.floor((now - then) / 1000);
3138
+ if (diff < 60) return 'just now';
3139
+ if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
3140
+ if (diff < 86400) return Math.floor(diff / 3600) + 'h ago';
3141
+ if (diff < 604800) return Math.floor(diff / 86400) + 'd ago';
3142
+ return new Date(dateStr).toLocaleDateString();
3143
+ }
3144
+
3145
+
3146
+ /* ══════════════════════════════════════════════════════════════
3147
+ RENDER
3148
+ ══════════════════════════════════════════════════════════════ */
3149
+ function renderAll() {
3150
+ renderHeader(); renderBoardFilters(); renderBoard(); renderTableControls(); renderTableBody(); renderMsFilters(); renderMilestones(); renderMetrics();
3151
+ // Render overview if active
3152
+ var overviewView = document.getElementById('view-overview');
3153
+ if (overviewView && overviewView.classList.contains('active')) {
3154
+ renderOverview();
3155
+ }
3156
+ }
3157
+
3158
+ function renderHeader() {
3159
+ renderSidebarLogo();
3160
+
3161
+ var mw = document.getElementById('h-milestone-wrap');
3162
+ var am = D.milestones.find(function(m) { return m.status === 'active'; });
3163
+ if (am) {
3164
+ mw.style.display = '';
3165
+ document.getElementById('h-milestone-name').textContent = am.title || am.id || '';
3166
+ document.getElementById('h-milestone-progress').style.width = (am.progress || 0) + '%';
3167
+ } else { mw.style.display = 'none'; }
3168
+
3169
+ var sw = document.getElementById('h-sprint-wrap');
3170
+ var as = D.sprints.find(function(s) { return s.status === 'active'; });
3171
+ if (as) {
3172
+ sw.style.display = '';
3173
+ document.getElementById('h-sprint-name').textContent = as.goal || as.id || '';
3174
+ var dr = daysRemaining(as.end_date);
3175
+ document.getElementById('h-sprint-days').textContent = dr !== null ? (dr >= 0 ? dr + ' days left' : Math.abs(dr) + ' days over') : '';
3176
+ } else { sw.style.display = 'none'; }
3177
+
3178
+ var taskPlural = ENTITY_NAMES.task ? ENTITY_NAMES.task.plural : 'Tasks';
3179
+ var total = D.tasks.length;
3180
+ var done = D.tasks.filter(function(f) { return f.status === COMPLETED_STATUS; }).length;
3181
+ var inProgStatus = (D.config && D.config.statuses && D.config.statuses.task) ?
3182
+ ((D.config.statuses.task.find(function(s) { return s.icon === 'half-circle'; }) || {}).key || 'in-progress') : 'in-progress';
3183
+ var inProg = D.tasks.filter(function(f) { return f.status === inProgStatus; }).length;
3184
+ var vel = D.health && D.health.velocity != null ? D.health.velocity : (total ? Math.round(done / total * 100) : 0);
3185
+ document.getElementById('h-stats').innerHTML =
3186
+ '<div class="stat"><span class="stat-val">' + total + '</span><span class="stat-label">' + escHtml(taskPlural) + '</span></div>' +
3187
+ '<div class="stat"><span class="stat-val" style="color:var(--success)">' + done + '</span><span class="stat-label">Done</span></div>' +
3188
+ '<div class="stat"><span class="stat-val" style="color:var(--warning)">' + inProg + '</span><span class="stat-label">In Progress</span></div>' +
3189
+ '<div class="stat"><span class="stat-val" style="color:var(--accent)">' + vel + '%</span><span class="stat-label">Velocity</span></div>';
3190
+ }
3191
+
1701
3192
  /* ══════════════════════════════════════════════════════════════
1702
3193
  NAVIGATION
1703
3194
  ══════════════════════════════════════════════════════════════ */
@@ -1714,6 +3205,10 @@ function switchView(name) {
1714
3205
  if (name === 'overview' && D.loaded) {
1715
3206
  renderOverview();
1716
3207
  }
3208
+ // Trigger notes rendering when switching to it
3209
+ if (name === 'notes' && D.loaded) {
3210
+ renderNotes();
3211
+ }
1717
3212
  }
1718
3213
 
1719
3214
  document.getElementById('sidebar-nav').addEventListener('click', function(e) {
@@ -1730,11 +3225,119 @@ function handleHash() {
1730
3225
  try { hash = localStorage.getItem('mdboard-view') || ''; } catch(e) {}
1731
3226
  }
1732
3227
  hash = hash || 'board';
1733
- var valid = ['board','table','milestones','metrics','overview'];
3228
+ var valid = ['board','table','milestones','metrics','overview','notes'];
1734
3229
  switchView(valid.indexOf(hash) !== -1 ? hash : 'board');
1735
3230
  }
1736
3231
  window.addEventListener('hashchange', handleHash);
1737
3232
 
3233
+ /* ══════════════════════════════════════════════════════════════
3234
+ SETTINGS PANEL
3235
+ ══════════════════════════════════════════════════════════════ */
3236
+ var _settingsState = { selectedTheme: null, originalTheme: null };
3237
+
3238
+ function openSettings() {
3239
+ var active = getActiveTheme();
3240
+ _settingsState.originalTheme = active;
3241
+ _settingsState.selectedTheme = null;
3242
+ document.getElementById('settings-overlay').classList.add('open');
3243
+ document.getElementById('settings-panel').classList.add('open');
3244
+ var toggle = document.getElementById('theme-default-toggle');
3245
+ if (toggle) toggle.checked = false;
3246
+ renderThemeGrid();
3247
+ }
3248
+
3249
+ function closeSettings() {
3250
+ if (_settingsState.selectedTheme) {
3251
+ revertThemePreview();
3252
+ _settingsState.selectedTheme = null;
3253
+ }
3254
+ document.getElementById('settings-overlay').classList.remove('open');
3255
+ document.getElementById('settings-panel').classList.remove('open');
3256
+ }
3257
+
3258
+ function renderThemeGrid() {
3259
+ var grid = document.getElementById('theme-grid');
3260
+ var info = document.getElementById('settings-theme-info');
3261
+ var active = getActiveTheme();
3262
+ var selected = _settingsState.selectedTheme;
3263
+
3264
+ if (info) {
3265
+ var displayId = selected || active;
3266
+ var displayTheme = THEMES[displayId];
3267
+ info.textContent = 'Active: ' + (THEMES[active] ? THEMES[active].name : active) +
3268
+ (selected && selected !== active ? ' → Preview: ' + (displayTheme ? displayTheme.name : selected) : '');
3269
+ }
3270
+
3271
+ var html = '';
3272
+ THEME_LIST.forEach(function(t) {
3273
+ var theme = THEMES[t.id];
3274
+ var v = theme.vars;
3275
+ var isSelected = selected ? t.id === selected : t.id === active;
3276
+ var cls = isSelected ? ' active' : '';
3277
+ html += '<div class="theme-card' + cls + '" data-theme="' + t.id + '">' +
3278
+ '<div class="theme-card-preview">' +
3279
+ '<span style="background:' + v.bg + '"></span>' +
3280
+ '<span style="background:' + v.surface + '"></span>' +
3281
+ '<span style="background:' + v.accent + '"></span>' +
3282
+ '<span style="background:' + v.success + '"></span>' +
3283
+ '<span style="background:' + v.warning + '"></span>' +
3284
+ '</div>' +
3285
+ '<div class="theme-card-name">' + escHtml(theme.name) + '</div>' +
3286
+ '<div class="theme-card-type">' + theme.type + '</div>' +
3287
+ '</div>';
3288
+ });
3289
+ grid.innerHTML = html;
3290
+ }
3291
+
3292
+ function saveTheme() {
3293
+ var themeId = _settingsState.selectedTheme;
3294
+ if (!themeId) {
3295
+ closeSettings();
3296
+ return;
3297
+ }
3298
+ var toggle = document.getElementById('theme-default-toggle');
3299
+ var setDefault = toggle ? toggle.checked : false;
3300
+ var css = generateThemeCss(themeId);
3301
+
3302
+ fetch('/api/theme', {
3303
+ method: 'POST',
3304
+ headers: { 'Content-Type': 'application/json' },
3305
+ body: JSON.stringify({ themeId: themeId, css: css, setDefault: setDefault })
3306
+ }).then(function(r) { return r.json(); }).then(function(data) {
3307
+ if (data && data.ok) {
3308
+ revertThemePreview();
3309
+ reloadCustomCss();
3310
+ D.config.theme = themeId;
3311
+ _settingsState.selectedTheme = null;
3312
+ showToast('Theme saved: ' + (THEMES[themeId] ? THEMES[themeId].name : themeId), 'success');
3313
+ closeSettings();
3314
+ } else {
3315
+ showToast('Error: ' + (data.error || 'Unknown'), 'error');
3316
+ }
3317
+ }).catch(function(e) {
3318
+ showToast('Error: ' + e.message, 'error');
3319
+ });
3320
+ }
3321
+
3322
+ document.getElementById('settings-toggle').addEventListener('click', function(e) {
3323
+ e.preventDefault();
3324
+ openSettings();
3325
+ });
3326
+
3327
+ document.getElementById('settings-close').addEventListener('click', closeSettings);
3328
+ document.getElementById('settings-overlay').addEventListener('click', closeSettings);
3329
+
3330
+ document.getElementById('theme-grid').addEventListener('click', function(e) {
3331
+ var card = e.target.closest('.theme-card');
3332
+ if (!card) return;
3333
+ var themeId = card.dataset.theme;
3334
+ _settingsState.selectedTheme = themeId;
3335
+ applyTheme(themeId);
3336
+ renderThemeGrid();
3337
+ });
3338
+
3339
+ document.getElementById('settings-save').addEventListener('click', saveTheme);
3340
+
1738
3341
  /* ══════════════════════════════════════════════════════════════
1739
3342
  INIT
1740
3343
  ══════════════════════════════════════════════════════════════ */
@@ -1783,6 +3386,22 @@ window.addEventListener('hashchange', handleHash);
1783
3386
  renderAll();
1784
3387
  connectSSE();
1785
3388
 
3389
+ // Auto-open history modal if no project
3390
+ checkAutoOpenHistory();
3391
+
3392
+ // Keyboard shortcut: Ctrl/Cmd+K to open history modal
3393
+ document.addEventListener('keydown', function(e) {
3394
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
3395
+ e.preventDefault();
3396
+ openHistoryModal();
3397
+ }
3398
+ });
3399
+
3400
+ // Load AI suggestions for autocomplete
3401
+ fetchJson('/api/ai-suggestions').then(function(data) {
3402
+ D.aiSuggestions = data || { skills: [], agents: [], mcps: [], commands: [], context: [] };
3403
+ });
3404
+
1786
3405
  // Load overview links in background for reverse link display
1787
3406
  if (hasWorkspace()) {
1788
3407
  fetchJson('/api/overview/links').then(function(data) {