@phren/cli 0.0.19 → 0.0.21

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.
@@ -588,9 +588,7 @@ export function renderProjectReferenceEnhancementScript(_authToken) {
588
588
  });
589
589
  })();`;
590
590
  }
591
- export function renderReviewQueueEditSyncScript() {
592
- return "";
593
- }
591
+ // renderReviewQueueEditSyncScript removed — was dead code returning ""
594
592
  export function renderTasksAndSettingsScript(authToken) {
595
593
  const safeToken = JSON.stringify(authToken).slice(1, -1);
596
594
  return `(function() {
@@ -606,27 +604,11 @@ export function renderTasksAndSettingsScript(authToken) {
606
604
  return fetch(url).then(function(r) { return r.json(); });
607
605
  }
608
606
 
609
- var _taskViewMode = 'list';
610
-
611
607
  function priorityBadge(p) {
612
608
  if (!p) return '';
613
- var colors = { high: '#ef4444', medium: '#f59e0b', low: '#6b7280' };
614
- var color = colors[p] || '#6b7280';
615
609
  return '<span class="task-priority-badge task-priority-' + esc(p) + '">' + esc(p) + '</span>';
616
610
  }
617
611
 
618
- function sessionBadge(sessionId) {
619
- if (!sessionId) return '';
620
- var short = sessionId.length > 8 ? sessionId.slice(0, 8) : sessionId;
621
- return '<span class="task-session-badge" title="Session ' + esc(sessionId) + '">' + esc(short) + '</span>';
622
- }
623
-
624
- function statusChip(section, checked) {
625
- if (checked || section === 'Done') return '<span class="task-status-chip task-status-done" title="Done"><svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M3 8.5l3.5 3.5 6.5-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> Done</span>';
626
- if (section === 'Active') return '<span class="task-status-chip task-status-active" title="In Progress">In Progress</span>';
627
- return '<span class="task-status-chip task-status-pending" title="Pending">Pending</span>';
628
- }
629
-
630
612
  function projectBadge(proj) {
631
613
  return '<span class="task-project-badge">' + esc(proj) + '</span>';
632
614
  }
@@ -733,55 +715,40 @@ export function renderTasksAndSettingsScript(authToken) {
733
715
  var container = document.getElementById('tasks-list');
734
716
  if (!container) return;
735
717
  if (!tasks.length && !doneTasks.length) {
736
- container.innerHTML = '<div class="task-empty-state"><svg viewBox="0 0 48 48" width="64" height="64" style="display:block;margin:0 auto 16px"><ellipse cx="24" cy="24" rx="16" ry="15" fill="#7B68AE" opacity="0.25"/><ellipse cx="24" cy="24" rx="12" ry="11.5" fill="#7B68AE" opacity="0.4"/><circle cx="19" cy="22" r="1.5" fill="#2D2255"/><circle cx="29" cy="22" r="1.5" fill="#2D2255"/><path d="M21 28c1 1.2 2.5 1.5 3.5 1.3 1-.2 2-1 2.5-1.3" stroke="#2D2255" stroke-width="1" fill="none" stroke-linecap="round"/></svg><div style="font-size:var(--text-md);font-weight:600;color:var(--ink);margin-bottom:4px">Nothing to do!</div><div style="color:var(--muted);font-size:var(--text-sm)">Add a task to get started.</div></div>';
718
+ container.innerHTML = '<div class="task-empty-state"><svg viewBox="0 0 48 48" width="48" height="48" style="display:block;margin:0 auto 12px"><ellipse cx="24" cy="24" rx="16" ry="15" fill="#7B68AE" opacity="0.25"/><ellipse cx="24" cy="24" rx="12" ry="11.5" fill="#7B68AE" opacity="0.4"/><circle cx="19" cy="22" r="1.5" fill="#2D2255"/><circle cx="29" cy="22" r="1.5" fill="#2D2255"/><path d="M21 28c1 1.2 2.5 1.5 3.5 1.3 1-.2 2-1 2.5-1.3" stroke="#2D2255" stroke-width="1" fill="none" stroke-linecap="round"/></svg><div style="font-size:var(--text-md);font-weight:600;color:var(--ink);margin-bottom:4px">Nothing to do!</div><div style="color:var(--muted);font-size:var(--text-sm)">Add a task to get started.</div></div>';
737
719
  return;
738
720
  }
739
721
 
740
- // Group by priority within sections
741
722
  var priorityOrder = { high: 0, medium: 1, low: 2 };
742
723
  function sortByPriority(a, b) {
743
- // Unchecked before checked
744
- var aChecked = a.checked || a.section === 'Done' ? 1 : 0;
745
- var bChecked = b.checked || b.section === 'Done' ? 1 : 0;
746
- if (aChecked !== bChecked) return aChecked - bChecked;
747
- // Pinned first
748
724
  if (a.pinned && !b.pinned) return -1;
749
725
  if (!a.pinned && b.pinned) return 1;
750
- // Then by priority
751
726
  var pa = priorityOrder[a.priority] !== undefined ? priorityOrder[a.priority] : 1;
752
727
  var pb = priorityOrder[b.priority] !== undefined ? priorityOrder[b.priority] : 1;
753
728
  return pa - pb;
754
729
  }
755
730
 
756
- // Separate into priority groups (exclude checked tasks even if not in Done section)
757
- function isActive(t) { return t.section !== 'Done' && !t.checked; }
758
- var high = tasks.filter(function(t) { return t.priority === 'high' && isActive(t); }).sort(sortByPriority);
759
- var medium = tasks.filter(function(t) { return t.priority === 'medium' && isActive(t); }).sort(sortByPriority);
760
- var low = tasks.filter(function(t) { return t.priority === 'low' && isActive(t); }).sort(sortByPriority);
761
- var noPriority = tasks.filter(function(t) { return !t.priority && isActive(t); }).sort(sortByPriority);
762
- var doneVisible = tasks.filter(function(t) { return t.section === 'Done' || t.checked; });
763
-
764
- function renderTaskCard(t) {
765
- var borderClass = t.priority === 'high' ? ' task-card-high' : t.priority === 'medium' ? ' task-card-medium' : t.priority === 'low' ? ' task-card-low' : '';
766
- var doneClass = (t.section === 'Done' || t.checked) ? ' task-card-done' : '';
767
- var html = '<div class="task-card' + borderClass + doneClass + '">';
768
- html += '<div class="task-card-top">';
769
- html += statusChip(t.section, t.checked);
770
- html += projectBadge(t.project);
731
+ function isNotDone(t) { return t.section !== 'Done' && !t.checked; }
732
+
733
+ function renderTaskRow(t) {
734
+ var isDone = t.section === 'Done' || t.checked;
735
+ var priClass = t.priority ? 'task-row-priority-' + esc(t.priority) : 'task-row-priority-none';
736
+ var html = '<div class="task-row' + (isDone ? ' task-row-done' : '') + '">';
737
+ html += '<div class="task-row-priority ' + priClass + '"></div>';
738
+ html += '<div class="task-row-content">';
739
+ html += '<span class="task-row-text">' + esc(t.line) + '</span>';
740
+ html += '</div>';
741
+ html += '<div class="task-row-meta">';
771
742
  html += pinIndicator(t.pinned);
772
743
  html += githubBadge(t.githubIssue, t.githubUrl);
773
744
  html += priorityBadge(t.priority);
774
- html += sessionBadge(t.sessionId);
775
- html += '</div>';
776
- html += '<div class="task-card-body">';
777
- html += '<span class="task-card-text">' + esc(t.line) + '</span>';
778
- if (t.context) html += '<span class="task-card-context">' + esc(t.context) + '</span>';
745
+ html += projectBadge(t.project);
779
746
  html += '</div>';
780
- html += '<div class="task-card-actions">';
781
- if (t.section !== 'Done' && !t.checked) {
782
- html += '<button class="task-done-btn" data-ts-action="completeTask" data-project="' + esc(t.project) + '" data-item="' + esc(t.line) + '" title="Mark done"><svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M3 8.5l3.5 3.5 6.5-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> Done</button>';
747
+ html += '<div class="task-row-actions">';
748
+ if (!isDone) {
749
+ html += '<button class="task-action-btn task-action-complete" data-ts-action="completeTask" data-project="' + esc(t.project) + '" data-item="' + esc(t.line) + '" title="Mark done"><svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M3 8.5l3.5 3.5 6.5-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button>';
783
750
  }
784
- html += '<button class="task-remove-btn" data-ts-action="removeTask" data-project="' + esc(t.project) + '" data-item="' + esc(t.line) + '" title="Delete task" style="background:none;border:1px solid var(--border);border-radius:var(--radius-sm);padding:2px 8px;cursor:pointer;color:var(--muted);font-size:var(--text-xs)"><svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></button>';
751
+ html += '<button class="task-action-btn task-action-delete" data-ts-action="removeTask" data-project="' + esc(t.project) + '" data-item="' + esc(t.line) + '" title="Delete task"><svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></button>';
785
752
  html += '</div>';
786
753
  html += '</div>';
787
754
  return html;
@@ -807,49 +774,42 @@ export function renderTasksAndSettingsScript(authToken) {
807
774
  topProjects.forEach(function(p) { html += '<span class="task-summary-project">' + esc(p) + ' (' + projectCounts[p] + ')</span>'; });
808
775
  html += '</span>';
809
776
  }
810
- html += '<span class="task-view-toggle" style="margin-left:auto">';
811
- html += '<button class="task-view-btn' + (_taskViewMode === 'list' ? ' active' : '') + '" data-ts-action="setTaskView" data-mode="list" title="List view"><svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M2 4h12M2 8h12M2 12h12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></button>';
812
- html += '<button class="task-view-btn' + (_taskViewMode === 'compact' ? ' active' : '') + '" data-ts-action="setTaskView" data-mode="compact" title="Compact view"><svg width="14" height="14" viewBox="0 0 16 16" fill="none"><rect x="1" y="2" width="6" height="5" rx="1" stroke="currentColor" stroke-width="1.2"/><rect x="9" y="2" width="6" height="5" rx="1" stroke="currentColor" stroke-width="1.2"/><rect x="1" y="9" width="6" height="5" rx="1" stroke="currentColor" stroke-width="1.2"/><rect x="9" y="9" width="6" height="5" rx="1" stroke="currentColor" stroke-width="1.2"/></svg></button>';
813
- html += '</span>';
814
777
  html += '</div>';
815
778
 
816
779
  // Add task input at top (only when a specific project is selected)
817
- var projects = projectFilter ? [projectFilter] : [];
818
- projects.forEach(function(proj) {
780
+ if (projectFilter) {
819
781
  html += '<div class="task-add-bar">';
820
- html += '<input id="task-add-input-' + esc(proj) + '" type="text" class="task-add-input" placeholder="Add a task to ' + esc(proj) + '\u2026" data-ts-action="addTaskKeydown" data-project="' + esc(proj) + '">';
821
- html += '<button class="task-add-btn" data-ts-action="addTask" data-project="' + esc(proj) + '"><svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg> Add</button>';
782
+ html += '<input id="task-add-input-' + esc(projectFilter) + '" type="text" class="task-add-input" placeholder="Add a task to ' + esc(projectFilter) + '\u2026" data-ts-action="addTaskKeydown" data-project="' + esc(projectFilter) + '">';
783
+ html += '<button class="task-add-btn" data-ts-action="addTask" data-project="' + esc(projectFilter) + '"><svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg> Add</button>';
822
784
  html += '</div>';
823
- });
785
+ }
824
786
 
825
- // Priority sections
826
- function renderSection(title, items, icon) {
787
+ // Group by section: Active first, then Queue
788
+ var activeTasks = tasks.filter(function(t) { return t.section === 'Active' && isNotDone(t); }).sort(sortByPriority);
789
+ var queueTasks = tasks.filter(function(t) { return t.section !== 'Active' && isNotDone(t); }).sort(sortByPriority);
790
+
791
+ function renderSection(title, items) {
827
792
  if (!items.length) return '';
828
- var gridClass = _taskViewMode === 'compact' ? 'task-card-grid task-card-grid-compact' : 'task-card-grid';
829
- var shtml = '<div class="task-priority-section">';
830
- shtml += '<div class="task-section-header"><span class="task-section-icon">' + icon + '</span> ' + title + ' <span class="task-section-count">' + items.length + '</span></div>';
831
- shtml += '<div class="' + gridClass + '">';
832
- items.forEach(function(t) { shtml += renderTaskCard(t); });
793
+ var shtml = '<div class="task-section-group">';
794
+ shtml += '<div class="task-section-header">' + title + ' <span class="task-section-count">' + items.length + '</span></div>';
795
+ shtml += '<div class="task-list">';
796
+ items.forEach(function(t) { shtml += renderTaskRow(t); });
833
797
  shtml += '</div></div>';
834
798
  return shtml;
835
799
  }
836
800
 
837
- html += renderSection('High Priority', high, '<svg width="10" height="10" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" fill="#ef4444"/></svg>');
838
- html += renderSection('Medium Priority', medium, '<svg width="10" height="10" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" fill="#f59e0b"/></svg>');
839
- html += renderSection('Low Priority', low, '<svg width="10" height="10" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" fill="#6b7280"/></svg>');
840
- if (noPriority.length) {
841
- html += renderSection('Tasks', noPriority, '<svg width="10" height="10" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" fill="var(--accent)"/></svg>');
842
- }
801
+ html += renderSection('Active', activeTasks);
802
+ html += renderSection('Queue', queueTasks);
843
803
 
844
804
  // Done section (collapsible)
845
- var allDone = showDone ? doneVisible : doneTasks;
805
+ var allDone = showDone ? tasks.filter(function(t) { return t.section === 'Done' || t.checked; }) : doneTasks;
846
806
  if (allDone.length) {
847
807
  html += '<div class="task-done-section">';
848
808
  html += '<button class="task-done-toggle" data-ts-action="toggleDoneSection">';
849
809
  html += '<span class="task-toggle-arrow">\u25B6</span> Completed <span class="task-section-count">' + allDone.length + '</span></button>';
850
810
  html += '<div class="task-done-list" style="display:none">';
851
- html += '<div class="task-card-grid">';
852
- allDone.forEach(function(t) { html += renderTaskCard(t); });
811
+ html += '<div class="task-list">';
812
+ allDone.forEach(function(t) { html += renderTaskRow(t); });
853
813
  html += '</div></div></div>';
854
814
  }
855
815
 
@@ -1172,122 +1132,13 @@ export function renderTasksAndSettingsScript(authToken) {
1172
1132
  var _origSwitchTab = window.switchTab;
1173
1133
  var _tasksLoaded = false;
1174
1134
  var _settingsLoaded = false;
1175
- var _sessionsLoaded = false;
1176
1135
  window.switchTab = function(tab) {
1177
1136
  if (typeof _origSwitchTab === 'function') _origSwitchTab(tab);
1178
1137
  if (tab === 'tasks' && !_tasksLoaded) { _tasksLoaded = true; loadTasks(); }
1179
1138
  if (tab === 'settings' && !_settingsLoaded) { _settingsLoaded = true; loadSettings(); }
1180
- if (tab === 'sessions' && !_sessionsLoaded) { _sessionsLoaded = true; loadSessions(); }
1181
- };
1182
-
1183
- var _sessionsData = [];
1184
- window.loadSessions = function() {
1185
- var projectFilter = document.getElementById('sessions-filter-project');
1186
- var project = projectFilter ? projectFilter.value : '';
1187
- var apiUrl = _tsAuthToken ? tsAuthUrl('/api/sessions') : '/api/sessions';
1188
- if (project) apiUrl += (apiUrl.includes('?') ? '&' : '?') + 'project=' + encodeURIComponent(project);
1189
- loadJson(apiUrl).then(function(data) {
1190
- if (!data.ok) throw new Error(data.error || 'Failed to load sessions');
1191
- _sessionsData = data.sessions || [];
1192
- renderSessionsList(_sessionsData);
1193
- // Populate project filter
1194
- if (projectFilter && projectFilter.options.length <= 1) {
1195
- var projects = {};
1196
- _sessionsData.forEach(function(s) { if (s.project) projects[s.project] = true; });
1197
- Object.keys(projects).sort().forEach(function(p) {
1198
- var opt = document.createElement('option');
1199
- opt.value = p; opt.textContent = p;
1200
- projectFilter.appendChild(opt);
1201
- });
1202
- }
1203
- }).catch(function(err) {
1204
- var list = document.getElementById('sessions-list');
1205
- if (list) list.innerHTML = '<div style="padding:40px;color:var(--muted);text-align:center">' + esc(err.message) + '</div>';
1206
- });
1207
- };
1208
-
1209
- function renderSessionsList(sessions) {
1210
- var list = document.getElementById('sessions-list');
1211
- var detail = document.getElementById('session-detail');
1212
- var countEl = document.getElementById('sessions-count');
1213
- if (detail) detail.style.display = 'none';
1214
- if (countEl) countEl.textContent = sessions.length + ' session(s)';
1215
- if (!list) return;
1216
- if (sessions.length === 0) {
1217
- list.innerHTML = '<div style="padding:40px;color:var(--muted);text-align:center"><svg viewBox="0 0 32 32" width="32" height="32" style="display:block;margin:0 auto 12px"><ellipse cx="16" cy="16" rx="10" ry="9.5" fill="#7B68AE" opacity="0.4"/><path d="M12 15l1-1 1 1-1 1z" fill="#2D2255"/><path d="M18 15l1-1 1 1-1 1z" fill="#2D2255"/><path d="M15 18.5c0.3-0.3 0.7-0.3 1 0" stroke="#2D2255" stroke-width="0.6" fill="none"/></svg>No sessions found.</div>';
1218
- return;
1219
- }
1220
- list.innerHTML = '<table style="width:100%;border-collapse:collapse;font-size:var(--text-sm)">' +
1221
- '<thead><tr style="border-bottom:1px solid var(--border);text-align:left">' +
1222
- '<th style="padding:8px">Session</th><th style="padding:8px">Project</th><th style="padding:8px">Started</th>' +
1223
- '<th style="padding:8px">Ended</th><th style="padding:8px">Duration</th><th style="padding:8px">Findings</th></tr></thead><tbody>' +
1224
- sessions.map(function(s) {
1225
- var id = s.sessionId.slice(0, 8);
1226
- var startDate = (s.startedAt || '').slice(0, 16).replace('T', ' ');
1227
- var endDate = s.endedAt ? (s.endedAt || '').slice(0, 16).replace('T', ' ') : '<span style="color:var(--green)">active</span>';
1228
- var dur = s.durationMins != null ? s.durationMins + 'm' : '—';
1229
- var summarySnip = s.summary ? '<div class="text-muted" style="font-size:var(--text-xs);max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + esc(s.summary.slice(0, 80)) + '</div>' : '';
1230
- return '<tr style="border-bottom:1px solid var(--border);cursor:pointer" data-ts-action="showSessionDetail" data-session-id="' + esc(s.sessionId) + '">' +
1231
- '<td style="padding:8px;font-family:monospace">' + esc(id) + summarySnip + '</td>' +
1232
- '<td style="padding:8px">' + esc(s.project || '—') + '</td>' +
1233
- '<td style="padding:8px">' + esc(startDate) + '</td>' +
1234
- '<td style="padding:8px">' + endDate + '</td>' +
1235
- '<td style="padding:8px">' + esc(dur) + '</td>' +
1236
- '<td style="padding:8px">' + (s.findingsAdded || 0) + '</td></tr>';
1237
- }).join('') + '</tbody></table>';
1238
- }
1239
-
1240
- window.showSessionDetail = function(sessionId) {
1241
- var apiUrl = _tsAuthToken ? tsAuthUrl('/api/sessions') : '/api/sessions';
1242
- apiUrl += (apiUrl.includes('?') ? '&' : '?') + 'sessionId=' + encodeURIComponent(sessionId);
1243
- var list = document.getElementById('sessions-list');
1244
- var detail = document.getElementById('session-detail');
1245
- if (list) list.style.display = 'none';
1246
- if (detail) { detail.style.display = 'block'; detail.innerHTML = '<div style="padding:20px;color:var(--muted)">Loading session...</div>'; }
1247
- loadJson(apiUrl).then(function(data) {
1248
- if (!data.ok) throw new Error(data.error || 'Session not found');
1249
- var s = data.session;
1250
- var findings = data.findings || [];
1251
- var tasks = data.tasks || [];
1252
- var date = (s.startedAt || '').slice(0, 16).replace('T', ' ');
1253
- var dur = s.durationMins != null ? s.durationMins + ' min' : '—';
1254
- var html = '<div style="margin-bottom:12px"><button class="btn btn-sm" data-ts-action="backToSessionsList">← Back</button></div>' +
1255
- '<div class="card" style="margin-bottom:16px"><div class="card-body">' +
1256
- '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px">' +
1257
- '<div><strong>Session</strong><div class="text-muted" style="font-family:monospace">' + esc(s.sessionId.slice(0, 8)) + '</div></div>' +
1258
- '<div><strong>Project</strong><div class="text-muted">' + esc(s.project || '—') + '</div></div>' +
1259
- '<div><strong>Date</strong><div class="text-muted">' + esc(date) + '</div></div>' +
1260
- '<div><strong>Duration</strong><div class="text-muted">' + esc(dur) + '</div></div>' +
1261
- '<div><strong>Findings</strong><div class="text-muted">' + findings.length + '</div></div>' +
1262
- '<div><strong>Tasks</strong><div class="text-muted">' + tasks.length + '</div></div>' +
1263
- '<div><strong>Status</strong><div class="text-muted">' + esc(s.status) + '</div></div>' +
1264
- '</div>' +
1265
- (s.summary ? '<div style="margin-top:12px;padding:12px;background:var(--bg);border-radius:var(--radius-sm)"><strong>Summary</strong><div style="margin-top:4px">' + esc(s.summary) + '</div></div>' : '') +
1266
- '</div></div>';
1267
- if (findings.length > 0) {
1268
- html += '<div class="card" style="margin-bottom:16px"><div class="card-header"><h3>Findings (' + findings.length + ')</h3></div><div class="card-body"><ul style="margin:0;padding-left:20px">';
1269
- findings.forEach(function(f) { html += '<li style="margin-bottom:4px"><span class="text-muted">[' + esc(f.project) + ']</span> ' + esc(f.text) + '</li>'; });
1270
- html += '</ul></div></div>';
1271
- }
1272
- if (tasks.length > 0) {
1273
- html += '<div class="card" style="margin-bottom:16px"><div class="card-header"><h3>Tasks (' + tasks.length + ')</h3></div><div class="card-body"><ul style="margin:0;padding-left:20px">';
1274
- tasks.forEach(function(t) { html += '<li style="margin-bottom:4px"><span class="text-muted">[' + esc(t.project) + '/' + esc(t.section) + ']</span> ' + esc(t.text) + '</li>'; });
1275
- html += '</ul></div></div>';
1276
- }
1277
- if (detail) detail.innerHTML = html;
1278
- }).catch(function(err) {
1279
- if (detail) detail.innerHTML = '<div style="padding:20px;color:var(--muted)">' + esc(err.message) + '</div>';
1280
- });
1281
- };
1282
-
1283
- window.backToSessionsList = function() {
1284
- var list = document.getElementById('sessions-list');
1285
- var detail = document.getElementById('session-detail');
1286
- if (list) list.style.display = 'block';
1287
- if (detail) detail.style.display = 'none';
1288
1139
  };
1289
1140
 
1290
- // Event delegation for dynamically generated tasks/settings/sessions UI
1141
+ // Event delegation for dynamically generated tasks/settings UI
1291
1142
  document.addEventListener('click', function(e) {
1292
1143
  var target = e.target;
1293
1144
  if (!target || typeof target.closest !== 'function') return;
@@ -1298,15 +1149,12 @@ export function renderTasksAndSettingsScript(authToken) {
1298
1149
  else if (action === 'completeTask') { completeTaskFromUi(actionEl.getAttribute('data-project'), actionEl.getAttribute('data-item')); }
1299
1150
  else if (action === 'removeTask') { removeTaskFromUi(actionEl.getAttribute('data-project'), actionEl.getAttribute('data-item')); }
1300
1151
  else if (action === 'addTask') { addTaskFromUi(actionEl.getAttribute('data-project')); }
1301
- else if (action === 'setTaskView') { _taskViewMode = actionEl.getAttribute('data-mode') || 'list'; filterTasks(); }
1302
1152
  else if (action === 'setFindingSensitivity') { setFindingSensitivity(actionEl.getAttribute('data-level')); }
1303
1153
  else if (action === 'toggleAutoCapture') { setAutoCapture(actionEl.getAttribute('data-enabled') !== 'true'); }
1304
1154
  else if (action === 'setTaskMode') { setTaskMode(actionEl.getAttribute('data-mode')); }
1305
1155
  else if (action === 'setProactivity') { setProactivity(actionEl.getAttribute('data-level')); }
1306
1156
  else if (action === 'toggleMcpEnabled') { setMcpEnabled(actionEl.getAttribute('data-enabled') !== 'true'); }
1307
1157
  else if (action === 'toggleIntegrationTool') { toggleIntegrationTool(actionEl.getAttribute('data-tool')); }
1308
- else if (action === 'showSessionDetail') { showSessionDetail(actionEl.getAttribute('data-session-id')); }
1309
- else if (action === 'backToSessionsList') { backToSessionsList(); }
1310
1158
  else if (action === 'setProjectFindingSensitivity') {
1311
1159
  var proj = getSettingsProject();
1312
1160
  var level = actionEl.getAttribute('data-level');
@@ -1625,10 +1473,6 @@ export function renderEventWiringScript() {
1625
1473
  var tasksFilterSection = document.getElementById('tasks-filter-section');
1626
1474
  if (tasksFilterSection) tasksFilterSection.addEventListener('change', function() { filterTasks(); });
1627
1475
 
1628
- // --- Sessions filter ---
1629
- var sessionsFilterProject = document.getElementById('sessions-filter-project');
1630
- if (sessionsFilterProject) sessionsFilterProject.addEventListener('change', function() { loadSessions(); });
1631
-
1632
1476
  // --- Mascot click animation ---
1633
1477
  var mascotSvg = document.querySelector('.header-brand svg');
1634
1478
  if (mascotSvg) {
@@ -1655,3 +1499,511 @@ export function renderEventWiringScript() {
1655
1499
  }
1656
1500
  })();`;
1657
1501
  }
1502
+ export function renderGraphHostScript() {
1503
+ return `(function() {
1504
+ var currentNode = null;
1505
+ var editMode = null;
1506
+
1507
+ function graphApi() {
1508
+ return window.phrenGraph || null;
1509
+ }
1510
+
1511
+ function esc(value) {
1512
+ return String(value)
1513
+ .replace(/&/g, '&amp;')
1514
+ .replace(/</g, '&lt;')
1515
+ .replace(/>/g, '&gt;')
1516
+ .replace(/"/g, '&quot;');
1517
+ }
1518
+
1519
+ function graphData() {
1520
+ var api = graphApi();
1521
+ return api && api.getData ? api.getData() : { nodes: [], links: [], topics: [], total: 0 };
1522
+ }
1523
+
1524
+ function authToken() {
1525
+ try {
1526
+ return new URL(window.location.href).searchParams.get('_auth') || '';
1527
+ } catch {
1528
+ return '';
1529
+ }
1530
+ }
1531
+
1532
+ function authUrl(path) {
1533
+ var token = authToken();
1534
+ if (!token) return path;
1535
+ return path + (path.indexOf('?') === -1 ? '?' : '&') + '_auth=' + encodeURIComponent(token);
1536
+ }
1537
+
1538
+ function graphToast(message, type) {
1539
+ var container = document.getElementById('toast-container');
1540
+ if (!container) return;
1541
+ var toast = document.createElement('div');
1542
+ toast.className = 'toast' + (type ? ' ' + type : '');
1543
+ toast.textContent = message;
1544
+ container.appendChild(toast);
1545
+ setTimeout(function() {
1546
+ if (toast.parentNode) toast.parentNode.removeChild(toast);
1547
+ }, 2600);
1548
+ }
1549
+
1550
+ function fetchCsrfToken() {
1551
+ return fetch(authUrl('/api/csrf-token')).then(function(r) { return r.json(); }).then(function(data) {
1552
+ return data && data.ok ? (data.token || null) : null;
1553
+ }).catch(function() { return null; });
1554
+ }
1555
+
1556
+ function formBody(fields, csrfToken) {
1557
+ var parts = [];
1558
+ Object.keys(fields).forEach(function(key) {
1559
+ var value = fields[key];
1560
+ if (value === undefined || value === null) return;
1561
+ parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(String(value)));
1562
+ });
1563
+ if (csrfToken) parts.push('_csrf=' + encodeURIComponent(csrfToken));
1564
+ return parts.join('&');
1565
+ }
1566
+
1567
+ function graphRequest(path, method, fields) {
1568
+ return fetchCsrfToken().then(function(csrfToken) {
1569
+ return fetch(authUrl(path), {
1570
+ method: method,
1571
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
1572
+ body: formBody(fields || {}, csrfToken)
1573
+ }).then(function(r) { return r.json(); });
1574
+ });
1575
+ }
1576
+
1577
+ function hidePopover() {
1578
+ currentNode = null;
1579
+ editMode = null;
1580
+ var popover = document.getElementById('graph-node-popover');
1581
+ if (popover) popover.style.display = 'none';
1582
+ if (typeof window.graphClearSelection === 'function') window.graphClearSelection();
1583
+ }
1584
+
1585
+ function positionPopover(x, y) {
1586
+ var popover = document.getElementById('graph-node-popover');
1587
+ var card = document.getElementById('graph-node-popover-card');
1588
+ var container = document.querySelector('#tab-graph .graph-container');
1589
+ if (!popover || !card || !container) return;
1590
+ popover.style.display = 'block';
1591
+ popover.style.visibility = 'hidden';
1592
+ requestAnimationFrame(function() {
1593
+ var containerRect = container.getBoundingClientRect();
1594
+ var cardRect = card.getBoundingClientRect();
1595
+ var left = Math.min(Math.max(12, x + 18), Math.max(12, containerRect.width - cardRect.width - 12));
1596
+ var top = Math.min(Math.max(12, y + 18), Math.max(12, containerRect.height - cardRect.height - 12));
1597
+ popover.style.left = left + 'px';
1598
+ popover.style.top = top + 'px';
1599
+ popover.style.visibility = 'visible';
1600
+ });
1601
+ }
1602
+
1603
+ function currentPopoverPoint() {
1604
+ var popover = document.getElementById('graph-node-popover');
1605
+ return {
1606
+ x: popover ? parseFloat(popover.style.left || '24') : 24,
1607
+ y: popover ? parseFloat(popover.style.top || '24') : 24
1608
+ };
1609
+ }
1610
+
1611
+ function neighborIds(nodeId) {
1612
+ var data = graphData();
1613
+ var ids = [];
1614
+ (data.links || []).forEach(function(link) {
1615
+ if (link.source === nodeId) ids.push(link.target);
1616
+ else if (link.target === nodeId) ids.push(link.source);
1617
+ });
1618
+ return ids;
1619
+ }
1620
+
1621
+ function nodeMap() {
1622
+ var data = graphData();
1623
+ var map = {};
1624
+ (data.nodes || []).forEach(function(node) { map[node.id] = node; });
1625
+ return map;
1626
+ }
1627
+
1628
+ function projectCounts(node) {
1629
+ var map = nodeMap();
1630
+ var counts = { finding: 0, task: 0, entity: 0, reference: 0, other: 0 };
1631
+ neighborIds(node.id).forEach(function(id) {
1632
+ var neighbor = map[id];
1633
+ if (!neighbor) return;
1634
+ var kind = neighbor.kind || 'other';
1635
+ if (counts[kind] === undefined) counts.other++;
1636
+ else counts[kind]++;
1637
+ });
1638
+ return counts;
1639
+ }
1640
+
1641
+ function kindLabel(node) {
1642
+ if (!node) return '';
1643
+ if (node.kind === 'entity') return node.entityType ? 'Fragment · ' + node.entityType : 'Fragment';
1644
+ if (node.kind === 'reference') return 'Reference';
1645
+ if (node.kind === 'task') return 'Task';
1646
+ if (node.kind === 'project') return 'Project';
1647
+ if (node.kind === 'finding') return node.topicLabel ? 'Finding · ' + node.topicLabel : 'Finding';
1648
+ return node.kind || 'Node';
1649
+ }
1650
+
1651
+ function chip(text, accent) {
1652
+ var border = accent ? 'var(--accent)' : 'var(--border)';
1653
+ var bg = accent ? 'var(--accent-dim)' : 'var(--surface-raised)';
1654
+ return '<span style="display:inline-flex;align-items:center;gap:6px;padding:4px 9px;border-radius:999px;border:1px solid ' + border + ';background:' + bg + ';font-size:11px;color:var(--ink)">' + esc(text) + '</span>';
1655
+ }
1656
+
1657
+ function scoreLine(node) {
1658
+ var score = typeof node.qualityScore === 'number' ? Math.round(node.qualityScore * 100) : null;
1659
+ return score ? chip('Quality ' + score, false) : '';
1660
+ }
1661
+
1662
+ function docChip(doc) {
1663
+ var border = 'var(--border)';
1664
+ var bg = 'var(--surface-raised)';
1665
+ return '<span data-doc-click="' + esc(doc) + '" style="display:inline-flex;align-items:center;gap:6px;padding:4px 9px;border-radius:999px;border:1px solid ' + border + ';background:' + bg + ';font-size:11px;color:var(--accent);cursor:pointer;text-decoration:underline dotted" title="Search for ' + esc(doc) + '">' + esc(doc) + '</span>';
1666
+ }
1667
+ function docsList(node) {
1668
+ var docs = (node.refDocs || []).map(function(ref) { return ref.doc; });
1669
+ if (!docs.length) return '';
1670
+ return '<div style="display:flex;flex-wrap:wrap;gap:8px">' + docs.slice(0, 12).map(function(doc) { return docChip(doc); }).join('') + '</div>';
1671
+ }
1672
+
1673
+ function renderView(node) {
1674
+ var title = node.displayLabel || node.label || node.tooltipLabel || node.id;
1675
+ var meta = [kindLabel(node)];
1676
+ if (node.projectName) meta.push(node.projectName);
1677
+ if (node.kind === 'task' && node.section) meta.push(node.section);
1678
+ if (node.kind === 'task' && node.priority) meta.push('Priority ' + node.priority);
1679
+ if (node.kind === 'finding' && node.topicLabel) meta.push(node.topicLabel);
1680
+
1681
+ var header = '<div style="display:flex;flex-direction:column;gap:8px;padding-right:44px"><div style="font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)">' + esc(kindLabel(node)) + '</div><div style="font-size:var(--text-lg);font-weight:600;line-height:1.2">' + esc(title) + '</div><div style="display:flex;flex-wrap:wrap;gap:8px">' + meta.filter(Boolean).map(function(item, index) { return chip(item, index === 0); }).join('') + scoreLine(node) + '</div></div>';
1682
+
1683
+ var body = '';
1684
+ var actions = [];
1685
+
1686
+ if (node.kind === 'project') {
1687
+ var counts = projectCounts(node);
1688
+ body += '<div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px">';
1689
+ body += '<div class="card" style="padding:12px"><div style="font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.05em">Findings</div><div style="font-size:var(--text-lg);font-weight:600;margin-top:4px">' + counts.finding + '</div></div>';
1690
+ body += '<div class="card" style="padding:12px"><div style="font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.05em">Tasks</div><div style="font-size:var(--text-lg);font-weight:600;margin-top:4px">' + counts.task + '</div></div>';
1691
+ body += '<div class="card" style="padding:12px"><div style="font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.05em">Fragments</div><div style="font-size:var(--text-lg);font-weight:600;margin-top:4px">' + counts.entity + '</div></div>';
1692
+ body += '<div class="card" style="padding:12px"><div style="font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.05em">References</div><div style="font-size:var(--text-lg);font-weight:600;margin-top:4px">' + counts.reference + '</div></div>';
1693
+ body += '</div>';
1694
+ } else if (node.kind === 'finding') {
1695
+ body += '<div id="graph-node-text" style="white-space:pre-wrap;line-height:1.65;font-size:var(--text-base)">' + esc(node.tooltipLabel || node.fullLabel || title) + '</div>';
1696
+ actions.push('<button type="button" class="btn btn-sm" data-graph-action="edit">Edit</button>');
1697
+ actions.push('<button type="button" class="btn btn-sm" data-graph-action="delete" style="border-color:var(--danger);color:var(--danger)">Delete</button>');
1698
+ } else if (node.kind === 'task') {
1699
+ body += '<div id="graph-node-text" style="white-space:pre-wrap;line-height:1.65;font-size:var(--text-base)">' + esc(node.tooltipLabel || node.fullLabel || title) + '</div>';
1700
+ body += '<div style="display:flex;flex-wrap:wrap;gap:8px;margin-top:12px">';
1701
+ body += chip('Status ' + (node.section || 'Queue'), true);
1702
+ if (node.priority) body += chip('Priority ' + node.priority, false);
1703
+ body += '</div>';
1704
+ actions.push('<button type="button" class="btn btn-sm" data-graph-action="edit">Edit</button>');
1705
+ if ((node.section || '').toLowerCase() !== 'done') actions.push('<button type="button" class="btn btn-sm" data-graph-action="complete">Done</button>');
1706
+ if ((node.section || '').toLowerCase() !== 'active') actions.push('<button type="button" class="btn btn-sm" data-graph-action="move-active">Move to Active</button>');
1707
+ if ((node.section || '').toLowerCase() !== 'queue') actions.push('<button type="button" class="btn btn-sm" data-graph-action="move-queue">Move to Queue</button>');
1708
+ actions.push('<button type="button" class="btn btn-sm" data-graph-action="delete" style="border-color:var(--danger);color:var(--danger)">Delete</button>');
1709
+ } else if (node.kind === 'entity') {
1710
+ if (node.connectedProjects && node.connectedProjects.length) {
1711
+ body += '<div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px">' + node.connectedProjects.map(function(project) { return chip(project, true); }).join('') + '</div>';
1712
+ }
1713
+ body += docsList(node) || '<div class="text-muted">No linked references.</div>';
1714
+ } else if (node.kind === 'reference') {
1715
+ body += '<div style="white-space:pre-wrap;line-height:1.6;font-size:var(--text-base)">' + esc(node.tooltipLabel || title) + '</div>';
1716
+ body += docsList(node);
1717
+ } else {
1718
+ body += '<div style="white-space:pre-wrap;line-height:1.6;font-size:var(--text-base)">' + esc(node.tooltipLabel || title) + '</div>';
1719
+ }
1720
+
1721
+ return header
1722
+ + '<div style="display:flex;flex-direction:column;gap:14px;margin-top:16px">' + body + '</div>'
1723
+ + (actions.length ? '<div style="display:flex;flex-wrap:wrap;gap:8px;margin-top:18px">' + actions.join('') + '</div>' : '');
1724
+ }
1725
+
1726
+ function renderEdit(node) {
1727
+ var title = node.kind === 'task' ? 'Edit task' : 'Edit finding';
1728
+ var text = node.tooltipLabel || node.fullLabel || node.displayLabel || '';
1729
+ var section = node.section || 'Queue';
1730
+ var priority = node.priority || '';
1731
+ var sectionControls = '';
1732
+ var priorityControls = '';
1733
+ if (node.kind === 'task') {
1734
+ sectionControls = '<label style="display:flex;flex-direction:column;gap:6px;font-size:12px;color:var(--muted)">Status<select id="graph-task-section" style="border:1px solid var(--border);border-radius:8px;padding:8px 10px;background:var(--surface);color:var(--ink)"><option value="Queue"' + (section === 'Queue' ? ' selected' : '') + '>Queue</option><option value="Active"' + (section === 'Active' ? ' selected' : '') + '>Active</option><option value="Done"' + (section === 'Done' ? ' selected' : '') + '>Done</option></select></label>';
1735
+ priorityControls = '<label style="display:flex;flex-direction:column;gap:6px;font-size:12px;color:var(--muted)">Priority<select id="graph-task-priority" style="border:1px solid var(--border);border-radius:8px;padding:8px 10px;background:var(--surface);color:var(--ink)"><option value=""' + (!priority ? ' selected' : '') + '>None</option><option value="high"' + (priority === 'high' ? ' selected' : '') + '>High</option><option value="medium"' + (priority === 'medium' ? ' selected' : '') + '>Medium</option><option value="low"' + (priority === 'low' ? ' selected' : '') + '>Low</option></select></label>';
1736
+ }
1737
+ return '<div style="display:flex;flex-direction:column;gap:8px;padding-right:44px"><div style="font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)">' + esc(title) + '</div><div style="font-size:var(--text-md);font-weight:600">' + esc(node.projectName || kindLabel(node)) + '</div></div>'
1738
+ + '<div style="display:flex;flex-direction:column;gap:12px;margin-top:16px">'
1739
+ + '<textarea id="graph-node-editor" style="min-height:180px;width:100%;border:1px solid var(--border);border-radius:12px;padding:12px 14px;background:var(--surface-sunken);color:var(--ink);font:inherit;line-height:1.55;resize:vertical">' + esc(text) + '</textarea>'
1740
+ + (node.kind === 'task' ? '<div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px">' + sectionControls + priorityControls + '</div>' : '')
1741
+ + '<div style="display:flex;flex-wrap:wrap;gap:8px"><button type="button" class="btn btn-sm" data-graph-action="save-edit">Save</button><button type="button" class="btn btn-sm" data-graph-action="cancel-edit">Cancel</button></div>'
1742
+ + '</div>';
1743
+ }
1744
+
1745
+ function renderPopover(node, x, y) {
1746
+ currentNode = node;
1747
+ var content = document.getElementById('graph-node-content');
1748
+ if (!content || !node) {
1749
+ currentNode = null;
1750
+ editMode = null;
1751
+ var popover = document.getElementById('graph-node-popover');
1752
+ if (popover) popover.style.display = 'none';
1753
+ return;
1754
+ }
1755
+ content.innerHTML = editMode ? renderEdit(node) : renderView(node);
1756
+ bindPopoverActions();
1757
+ positionPopover(x, y);
1758
+ }
1759
+
1760
+ function matchesNode(node, match) {
1761
+ if (!node || !match) return false;
1762
+ if (match.id && node.id !== match.id) return false;
1763
+ if (match.kind && node.kind !== match.kind) return false;
1764
+ if (match.projectName && node.projectName !== match.projectName) return false;
1765
+ if (match.tooltipLabel && (node.tooltipLabel || node.fullLabel || '') !== match.tooltipLabel) return false;
1766
+ if (match.displayLabel && (node.displayLabel || node.label || '') !== match.displayLabel) return false;
1767
+ return true;
1768
+ }
1769
+
1770
+ function reloadGraph(match) {
1771
+ return fetch(authUrl('/api/graph')).then(function(r) {
1772
+ if (!r.ok) throw new Error('HTTP ' + r.status);
1773
+ return r.json();
1774
+ }).then(function(data) {
1775
+ var api = graphApi();
1776
+ if (!api || !api.mount) return;
1777
+ api.mount(data);
1778
+ if (!match) {
1779
+ hidePopover();
1780
+ return;
1781
+ }
1782
+ var next = null;
1783
+ var dataNodes = api.getData ? api.getData().nodes : [];
1784
+ for (var index = 0; index < dataNodes.length; index++) {
1785
+ if (matchesNode(dataNodes[index], match)) {
1786
+ next = dataNodes[index];
1787
+ break;
1788
+ }
1789
+ }
1790
+ if (next && api.focusNode) api.focusNode(next.id);
1791
+ else hidePopover();
1792
+ });
1793
+ }
1794
+
1795
+ function saveFindingEdit() {
1796
+ var editor = document.getElementById('graph-node-editor');
1797
+ if (!editor || !currentNode) return;
1798
+ var nextText = editor.value.trim();
1799
+ if (!nextText || nextText === (currentNode.tooltipLabel || currentNode.fullLabel || '').trim()) {
1800
+ editMode = null;
1801
+ var point = currentPopoverPoint();
1802
+ renderPopover(currentNode, point.x, point.y);
1803
+ return;
1804
+ }
1805
+ graphRequest('/api/findings/' + encodeURIComponent(currentNode.projectName), 'PUT', {
1806
+ old_text: currentNode.tooltipLabel || currentNode.fullLabel || '',
1807
+ new_text: nextText
1808
+ }).then(function(result) {
1809
+ if (!result || !result.ok) throw new Error(result && result.error ? result.error : 'Save failed');
1810
+ graphToast('Finding updated', 'ok');
1811
+ editMode = null;
1812
+ return reloadGraph({ kind: 'finding', projectName: currentNode.projectName, tooltipLabel: nextText });
1813
+ }).catch(function(err) {
1814
+ graphToast('Update failed: ' + err.message, 'err');
1815
+ });
1816
+ }
1817
+
1818
+ function saveTaskEdit() {
1819
+ var editor = document.getElementById('graph-node-editor');
1820
+ var sectionEl = document.getElementById('graph-task-section');
1821
+ var priorityEl = document.getElementById('graph-task-priority');
1822
+ if (!editor || !currentNode) return;
1823
+ var nextText = editor.value.trim();
1824
+ if (!nextText) {
1825
+ graphToast('Task text cannot be empty', 'err');
1826
+ return;
1827
+ }
1828
+ var updates = { text: nextText };
1829
+ if (sectionEl && sectionEl.value) updates.section = sectionEl.value;
1830
+ if (priorityEl) updates.priority = priorityEl.value;
1831
+ graphRequest('/api/tasks/update', 'POST', {
1832
+ project: currentNode.projectName,
1833
+ item: currentNode.tooltipLabel || currentNode.fullLabel || currentNode.displayLabel || '',
1834
+ text: updates.text,
1835
+ section: updates.section || '',
1836
+ priority: updates.priority || ''
1837
+ }).then(function(result) {
1838
+ if (!result || !result.ok) throw new Error(result && result.error ? result.error : 'Save failed');
1839
+ graphToast('Task updated', 'ok');
1840
+ editMode = null;
1841
+ var nextSection = updates.section || currentNode.section || 'Queue';
1842
+ if (nextSection === 'Done') {
1843
+ hidePopover();
1844
+ return reloadGraph(null);
1845
+ }
1846
+ return reloadGraph({ kind: 'task', projectName: currentNode.projectName, tooltipLabel: nextText });
1847
+ }).catch(function(err) {
1848
+ graphToast('Update failed: ' + err.message, 'err');
1849
+ });
1850
+ }
1851
+
1852
+ function deleteCurrentNode() {
1853
+ if (!currentNode) return;
1854
+ if (!confirm('Delete this ' + (currentNode.kind || 'node') + '?')) return;
1855
+ if (currentNode.kind === 'finding') {
1856
+ graphRequest('/api/findings/' + encodeURIComponent(currentNode.projectName), 'DELETE', {
1857
+ text: currentNode.tooltipLabel || currentNode.fullLabel || ''
1858
+ }).then(function(result) {
1859
+ if (!result || !result.ok) throw new Error(result && result.error ? result.error : 'Delete failed');
1860
+ graphToast('Finding deleted', 'ok');
1861
+ return reloadGraph(null);
1862
+ }).catch(function(err) {
1863
+ graphToast('Delete failed: ' + err.message, 'err');
1864
+ });
1865
+ return;
1866
+ }
1867
+ if (currentNode.kind === 'task') {
1868
+ graphRequest('/api/tasks/remove', 'POST', {
1869
+ project: currentNode.projectName,
1870
+ item: currentNode.tooltipLabel || currentNode.fullLabel || currentNode.displayLabel || ''
1871
+ }).then(function(result) {
1872
+ if (!result || !result.ok) throw new Error(result && result.error ? result.error : 'Delete failed');
1873
+ graphToast('Task removed', 'ok');
1874
+ return reloadGraph(null);
1875
+ }).catch(function(err) {
1876
+ graphToast('Delete failed: ' + err.message, 'err');
1877
+ });
1878
+ }
1879
+ }
1880
+
1881
+ function completeCurrentTask() {
1882
+ if (!currentNode) return;
1883
+ graphRequest('/api/tasks/complete', 'POST', {
1884
+ project: currentNode.projectName,
1885
+ item: currentNode.tooltipLabel || currentNode.fullLabel || currentNode.displayLabel || ''
1886
+ }).then(function(result) {
1887
+ if (!result || !result.ok) throw new Error(result && result.error ? result.error : 'Update failed');
1888
+ graphToast('Task completed', 'ok');
1889
+ return reloadGraph(null);
1890
+ }).catch(function(err) {
1891
+ graphToast('Update failed: ' + err.message, 'err');
1892
+ });
1893
+ }
1894
+
1895
+ function moveCurrentTask(section) {
1896
+ if (!currentNode) return;
1897
+ graphRequest('/api/tasks/update', 'POST', {
1898
+ project: currentNode.projectName,
1899
+ item: currentNode.tooltipLabel || currentNode.fullLabel || currentNode.displayLabel || '',
1900
+ section: section
1901
+ }).then(function(result) {
1902
+ if (!result || !result.ok) throw new Error(result && result.error ? result.error : 'Update failed');
1903
+ graphToast('Task moved to ' + section, 'ok');
1904
+ return reloadGraph({ kind: 'task', projectName: currentNode.projectName, tooltipLabel: currentNode.tooltipLabel || currentNode.fullLabel || currentNode.displayLabel || '' });
1905
+ }).catch(function(err) {
1906
+ graphToast('Update failed: ' + err.message, 'err');
1907
+ });
1908
+ }
1909
+
1910
+ function bindPopoverActions() {
1911
+ var closeBtn = document.getElementById('graph-node-close');
1912
+ if (closeBtn) closeBtn.onclick = hidePopover;
1913
+
1914
+ // Doc reference chips — click to search for the document
1915
+ document.querySelectorAll('[data-doc-click]').forEach(function(chip) {
1916
+ chip.addEventListener('click', function() {
1917
+ var doc = chip.getAttribute('data-doc-click') || '';
1918
+ if (!doc) return;
1919
+ // Search for this doc in the graph by updating the search filter
1920
+ var searchInput = document.querySelector('input[data-search-filter]');
1921
+ if (searchInput) {
1922
+ searchInput.value = doc.replace(/FINDINGS\\.md$/, '').replace(/\\/$/, '');
1923
+ searchInput.dispatchEvent(new Event('input', { bubbles: true }));
1924
+ }
1925
+ });
1926
+ });
1927
+
1928
+ document.querySelectorAll('[data-graph-action]').forEach(function(button) {
1929
+ button.addEventListener('click', function() {
1930
+ var action = button.getAttribute('data-graph-action');
1931
+ if (action === 'edit') {
1932
+ editMode = currentNode && (currentNode.kind === 'finding' || currentNode.kind === 'task') ? currentNode.kind : null;
1933
+ var point = currentPopoverPoint();
1934
+ renderPopover(currentNode, point.x, point.y);
1935
+ } else if (action === 'cancel-edit') {
1936
+ editMode = null;
1937
+ var point = currentPopoverPoint();
1938
+ renderPopover(currentNode, point.x, point.y);
1939
+ } else if (action === 'save-edit') {
1940
+ if (editMode === 'task') saveTaskEdit();
1941
+ else saveFindingEdit();
1942
+ } else if (action === 'delete') {
1943
+ deleteCurrentNode();
1944
+ } else if (action === 'complete') {
1945
+ completeCurrentTask();
1946
+ } else if (action === 'move-active') {
1947
+ moveCurrentTask('Active');
1948
+ } else if (action === 'move-queue') {
1949
+ moveCurrentTask('Queue');
1950
+ }
1951
+ });
1952
+ });
1953
+ }
1954
+
1955
+ function ensureHostBindings() {
1956
+ var api = graphApi();
1957
+ if (!api || !api.onNodeSelect) return false;
1958
+ api.onNodeSelect(function(node, x, y) {
1959
+ if (!node) {
1960
+ currentNode = null;
1961
+ editMode = null;
1962
+ var popover = document.getElementById('graph-node-popover');
1963
+ if (popover) popover.style.display = 'none';
1964
+ return;
1965
+ }
1966
+ editMode = null;
1967
+ renderPopover(node, x, y);
1968
+ });
1969
+ if (typeof api.onSelectionClear === 'function') {
1970
+ api.onSelectionClear(function() {
1971
+ currentNode = null;
1972
+ editMode = null;
1973
+ var popover = document.getElementById('graph-node-popover');
1974
+ if (popover) popover.style.display = 'none';
1975
+ });
1976
+ }
1977
+ return true;
1978
+ }
1979
+
1980
+ function onOutsidePointer(event) {
1981
+ var popover = document.getElementById('graph-node-popover-card');
1982
+ if (!currentNode || !popover) return;
1983
+ if (popover.contains(event.target)) return;
1984
+ hidePopover();
1985
+ }
1986
+
1987
+ function onEscape(event) {
1988
+ if (event.key !== 'Escape' || !currentNode) return;
1989
+ if (editMode) {
1990
+ editMode = null;
1991
+ var point = currentPopoverPoint();
1992
+ renderPopover(currentNode, point.x, point.y);
1993
+ return;
1994
+ }
1995
+ hidePopover();
1996
+ }
1997
+
1998
+ document.addEventListener('pointerdown', onOutsidePointer);
1999
+ document.addEventListener('keydown', onEscape);
2000
+
2001
+ if (!ensureHostBindings()) {
2002
+ var tries = 0;
2003
+ var timer = setInterval(function() {
2004
+ tries++;
2005
+ if (ensureHostBindings() || tries > 40) clearInterval(timer);
2006
+ }, 100);
2007
+ }
2008
+ })();`;
2009
+ }