@phren/cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +590 -0
  3. package/mcp/dist/capabilities/cli.js +61 -0
  4. package/mcp/dist/capabilities/index.js +15 -0
  5. package/mcp/dist/capabilities/mcp.js +61 -0
  6. package/mcp/dist/capabilities/types.js +57 -0
  7. package/mcp/dist/capabilities/vscode.js +61 -0
  8. package/mcp/dist/capabilities/web-ui.js +61 -0
  9. package/mcp/dist/cli-actions.js +302 -0
  10. package/mcp/dist/cli-config.js +580 -0
  11. package/mcp/dist/cli-extract.js +305 -0
  12. package/mcp/dist/cli-govern.js +371 -0
  13. package/mcp/dist/cli-graph.js +169 -0
  14. package/mcp/dist/cli-hooks-citations.js +44 -0
  15. package/mcp/dist/cli-hooks-context.js +56 -0
  16. package/mcp/dist/cli-hooks-globs.js +83 -0
  17. package/mcp/dist/cli-hooks-output.js +130 -0
  18. package/mcp/dist/cli-hooks-retrieval.js +2 -0
  19. package/mcp/dist/cli-hooks-session.js +1402 -0
  20. package/mcp/dist/cli-hooks.js +350 -0
  21. package/mcp/dist/cli-namespaces.js +989 -0
  22. package/mcp/dist/cli-ops.js +253 -0
  23. package/mcp/dist/cli-search.js +407 -0
  24. package/mcp/dist/cli.js +108 -0
  25. package/mcp/dist/content-archive.js +278 -0
  26. package/mcp/dist/content-citation.js +391 -0
  27. package/mcp/dist/content-dedup.js +622 -0
  28. package/mcp/dist/content-learning.js +472 -0
  29. package/mcp/dist/content-metadata.js +186 -0
  30. package/mcp/dist/content-validate.js +462 -0
  31. package/mcp/dist/core-finding.js +54 -0
  32. package/mcp/dist/core-project.js +36 -0
  33. package/mcp/dist/core-search.js +50 -0
  34. package/mcp/dist/data-access.js +400 -0
  35. package/mcp/dist/data-tasks.js +821 -0
  36. package/mcp/dist/embedding.js +344 -0
  37. package/mcp/dist/entrypoint.js +387 -0
  38. package/mcp/dist/finding-context.js +172 -0
  39. package/mcp/dist/finding-impact.js +181 -0
  40. package/mcp/dist/finding-journal.js +122 -0
  41. package/mcp/dist/finding-lifecycle.js +259 -0
  42. package/mcp/dist/governance-audit.js +22 -0
  43. package/mcp/dist/governance-locks.js +96 -0
  44. package/mcp/dist/governance-policy.js +648 -0
  45. package/mcp/dist/governance-scores.js +355 -0
  46. package/mcp/dist/hooks.js +449 -0
  47. package/mcp/dist/impact-scoring.js +22 -0
  48. package/mcp/dist/index-query.js +168 -0
  49. package/mcp/dist/index.js +205 -0
  50. package/mcp/dist/init-config.js +336 -0
  51. package/mcp/dist/init-preferences.js +62 -0
  52. package/mcp/dist/init-setup.js +1305 -0
  53. package/mcp/dist/init-shared.js +29 -0
  54. package/mcp/dist/init.js +1730 -0
  55. package/mcp/dist/link-checksums.js +62 -0
  56. package/mcp/dist/link-context.js +257 -0
  57. package/mcp/dist/link-doctor.js +591 -0
  58. package/mcp/dist/link-skills.js +212 -0
  59. package/mcp/dist/link.js +596 -0
  60. package/mcp/dist/logger.js +15 -0
  61. package/mcp/dist/machine-identity.js +38 -0
  62. package/mcp/dist/mcp-config.js +254 -0
  63. package/mcp/dist/mcp-data.js +315 -0
  64. package/mcp/dist/mcp-extract-facts.js +78 -0
  65. package/mcp/dist/mcp-extract.js +133 -0
  66. package/mcp/dist/mcp-finding.js +557 -0
  67. package/mcp/dist/mcp-graph.js +339 -0
  68. package/mcp/dist/mcp-hooks.js +256 -0
  69. package/mcp/dist/mcp-memory.js +58 -0
  70. package/mcp/dist/mcp-ops.js +328 -0
  71. package/mcp/dist/mcp-search.js +628 -0
  72. package/mcp/dist/mcp-session.js +651 -0
  73. package/mcp/dist/mcp-skills.js +189 -0
  74. package/mcp/dist/mcp-tasks.js +551 -0
  75. package/mcp/dist/mcp-types.js +7 -0
  76. package/mcp/dist/memory-ui-assets.js +6 -0
  77. package/mcp/dist/memory-ui-data.js +513 -0
  78. package/mcp/dist/memory-ui-graph.js +1910 -0
  79. package/mcp/dist/memory-ui-page.js +353 -0
  80. package/mcp/dist/memory-ui-scripts.js +1387 -0
  81. package/mcp/dist/memory-ui-server.js +1218 -0
  82. package/mcp/dist/memory-ui-styles.js +555 -0
  83. package/mcp/dist/memory-ui.js +9 -0
  84. package/mcp/dist/package-metadata.js +13 -0
  85. package/mcp/dist/phren-art.js +52 -0
  86. package/mcp/dist/phren-core.js +108 -0
  87. package/mcp/dist/phren-dotenv.js +67 -0
  88. package/mcp/dist/phren-paths.js +476 -0
  89. package/mcp/dist/proactivity.js +172 -0
  90. package/mcp/dist/profile-store.js +228 -0
  91. package/mcp/dist/project-config.js +85 -0
  92. package/mcp/dist/project-locator.js +25 -0
  93. package/mcp/dist/project-topics.js +1134 -0
  94. package/mcp/dist/provider-adapters.js +176 -0
  95. package/mcp/dist/runtime-profile.js +18 -0
  96. package/mcp/dist/session-checkpoints.js +131 -0
  97. package/mcp/dist/session-utils.js +68 -0
  98. package/mcp/dist/shared-content.js +8 -0
  99. package/mcp/dist/shared-embedding-cache.js +143 -0
  100. package/mcp/dist/shared-fragment-graph.js +456 -0
  101. package/mcp/dist/shared-governance.js +4 -0
  102. package/mcp/dist/shared-index.js +1334 -0
  103. package/mcp/dist/shared-ollama.js +192 -0
  104. package/mcp/dist/shared-paths.js +1 -0
  105. package/mcp/dist/shared-retrieval.js +796 -0
  106. package/mcp/dist/shared-search-fallback.js +375 -0
  107. package/mcp/dist/shared-sqljs.js +42 -0
  108. package/mcp/dist/shared-stemmer.js +171 -0
  109. package/mcp/dist/shared-vector-index.js +199 -0
  110. package/mcp/dist/shared.js +114 -0
  111. package/mcp/dist/shell-entry.js +209 -0
  112. package/mcp/dist/shell-input.js +943 -0
  113. package/mcp/dist/shell-palette.js +119 -0
  114. package/mcp/dist/shell-render.js +252 -0
  115. package/mcp/dist/shell-state-store.js +81 -0
  116. package/mcp/dist/shell-types.js +13 -0
  117. package/mcp/dist/shell-view-list.js +14 -0
  118. package/mcp/dist/shell-view.js +707 -0
  119. package/mcp/dist/shell.js +352 -0
  120. package/mcp/dist/skill-files.js +117 -0
  121. package/mcp/dist/skill-registry.js +279 -0
  122. package/mcp/dist/skill-state.js +28 -0
  123. package/mcp/dist/startup-embedding.js +57 -0
  124. package/mcp/dist/status.js +323 -0
  125. package/mcp/dist/synonyms.json +670 -0
  126. package/mcp/dist/task-hygiene.js +251 -0
  127. package/mcp/dist/task-lifecycle.js +347 -0
  128. package/mcp/dist/tasks-github.js +76 -0
  129. package/mcp/dist/telemetry.js +165 -0
  130. package/mcp/dist/test-global-setup.js +37 -0
  131. package/mcp/dist/tool-registry.js +104 -0
  132. package/mcp/dist/update.js +97 -0
  133. package/mcp/dist/utils.js +543 -0
  134. package/package.json +67 -0
  135. package/skills/README.md +7 -0
  136. package/skills/consolidate/SKILL.md +152 -0
  137. package/skills/discover/SKILL.md +175 -0
  138. package/skills/init/SKILL.md +216 -0
  139. package/skills/profiles/SKILL.md +121 -0
  140. package/skills/sync/SKILL.md +261 -0
  141. package/starter/README.md +74 -0
  142. package/starter/global/CLAUDE.md +89 -0
  143. package/starter/global/skills/humanize.md +30 -0
  144. package/starter/global/skills/pipeline.md +35 -0
  145. package/starter/global/skills/release.md +35 -0
  146. package/starter/machines.yaml +8 -0
  147. package/starter/my-api/.claude/skills/README.md +7 -0
  148. package/starter/my-api/CLAUDE.md +33 -0
  149. package/starter/my-api/FINDINGS.md +9 -0
  150. package/starter/my-api/summary.md +7 -0
  151. package/starter/my-api/tasks.md +7 -0
  152. package/starter/my-first-project/.claude/skills/README.md +7 -0
  153. package/starter/my-first-project/CLAUDE.md +49 -0
  154. package/starter/my-first-project/FINDINGS.md +24 -0
  155. package/starter/my-first-project/summary.md +11 -0
  156. package/starter/my-first-project/tasks.md +25 -0
  157. package/starter/my-frontend/.claude/skills/README.md +7 -0
  158. package/starter/my-frontend/CLAUDE.md +33 -0
  159. package/starter/my-frontend/FINDINGS.md +9 -0
  160. package/starter/my-frontend/summary.md +7 -0
  161. package/starter/my-frontend/tasks.md +7 -0
  162. package/starter/profiles/default.yaml +4 -0
  163. package/starter/profiles/personal.yaml +4 -0
  164. package/starter/profiles/work.yaml +4 -0
  165. package/starter/templates/README.md +7 -0
  166. package/starter/templates/frontend/CLAUDE.md +23 -0
  167. package/starter/templates/frontend/FINDINGS.md +7 -0
  168. package/starter/templates/frontend/reference/README.md +4 -0
  169. package/starter/templates/frontend/summary.md +7 -0
  170. package/starter/templates/frontend/tasks.md +11 -0
  171. package/starter/templates/library/CLAUDE.md +22 -0
  172. package/starter/templates/library/FINDINGS.md +7 -0
  173. package/starter/templates/library/reference/README.md +4 -0
  174. package/starter/templates/library/summary.md +7 -0
  175. package/starter/templates/library/tasks.md +11 -0
  176. package/starter/templates/monorepo/CLAUDE.md +21 -0
  177. package/starter/templates/monorepo/FINDINGS.md +7 -0
  178. package/starter/templates/monorepo/reference/README.md +4 -0
  179. package/starter/templates/monorepo/summary.md +7 -0
  180. package/starter/templates/monorepo/tasks.md +11 -0
  181. package/starter/templates/python-project/CLAUDE.md +21 -0
  182. package/starter/templates/python-project/FINDINGS.md +7 -0
  183. package/starter/templates/python-project/reference/README.md +4 -0
  184. package/starter/templates/python-project/summary.md +7 -0
  185. package/starter/templates/python-project/tasks.md +10 -0
@@ -0,0 +1,353 @@
1
+ import { WEB_UI_STYLES, renderWebUiScript } from "./memory-ui-assets.js";
2
+ import { renderGraphScript } from "./memory-ui-graph.js";
3
+ import { readSyncSnapshot } from "./memory-ui-data.js";
4
+ import { PROJECT_REFERENCE_UI_STYLES, SETTINGS_TAB_UI_STYLES, TASK_UI_STYLES } from "./memory-ui-styles.js";
5
+ import { renderSkillUiEnhancementScript, renderProjectReferenceEnhancementScript, renderReviewQueueEditSyncScript, renderTasksAndSettingsScript, renderSearchScript, renderEventWiringScript, } from "./memory-ui-scripts.js";
6
+ function h(s) {
7
+ return s
8
+ .replace(/&/g, "&")
9
+ .replace(/</g, "&lt;")
10
+ .replace(/>/g, "&gt;")
11
+ .replace(/"/g, "&quot;");
12
+ }
13
+ export function renderWebUiPage(phrenPath, authToken, nonce) {
14
+ const sync = readSyncSnapshot(phrenPath);
15
+ const nonceAttr = nonce ? ` nonce="${h(nonce)}"` : "";
16
+ return `<!doctype html>
17
+ <html lang="en">
18
+ <head>
19
+ <meta charset="utf-8" />
20
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
21
+ <link rel="preconnect" href="https://fonts.bunny.net" />
22
+ <link href="https://fonts.bunny.net/css?family=inter:400,500,600,700&display=swap" rel="stylesheet" />
23
+ <title>phren</title>
24
+ <script${nonceAttr} src="https://cdn.jsdelivr.net/npm/marked@12/marked.min.js"></script>
25
+ <style>
26
+ ${WEB_UI_STYLES}
27
+ ${PROJECT_REFERENCE_UI_STYLES}
28
+ ${SETTINGS_TAB_UI_STYLES}
29
+ ${TASK_UI_STYLES}
30
+ </style>
31
+ </head>
32
+ <body>
33
+
34
+ <div class="header">
35
+ <div class="header-brand">
36
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
37
+ <defs>
38
+ <radialGradient id="hdr-brain" cx="45%" cy="40%" r="55%">
39
+ <stop offset="0%" stop-color="#9B8BC4"/>
40
+ <stop offset="50%" stop-color="#7B68AE"/>
41
+ <stop offset="100%" stop-color="#5B4B8A"/>
42
+ </radialGradient>
43
+ </defs>
44
+ <!-- brain body -->
45
+ <ellipse cx="12" cy="12" rx="8" ry="7.5" fill="url(#hdr-brain)"/>
46
+ <!-- brain folds -->
47
+ <path d="M8 9.5c1.5-1 3-0.5 4 0s2.5 0.8 3.5-0.2" stroke="#5B4B8A" stroke-width="0.7" fill="none" stroke-linecap="round"/>
48
+ <path d="M7.5 12c2 0.8 3.5 0.2 5-0.5s3 0 4 0.8" stroke="#5B4B8A" stroke-width="0.6" fill="none" stroke-linecap="round"/>
49
+ <!-- eyes (dark diamonds) -->
50
+ <path d="M9 11l0.8-0.8 0.8 0.8-0.8 0.8z" fill="#2D2255"/>
51
+ <path d="M13.4 11l0.8-0.8 0.8 0.8-0.8 0.8z" fill="#2D2255"/>
52
+ <!-- smile -->
53
+ <path d="M10.5 13.5c0.5 0.4 1.2 0.5 2 0.1" stroke="#2D2255" stroke-width="0.5" fill="none" stroke-linecap="round"/>
54
+ <!-- legs -->
55
+ <rect x="9.5" y="18.5" width="1.5" height="2" rx="0.5" fill="#5B4B8A"/>
56
+ <rect x="13" y="18.5" width="1.5" height="2" rx="0.5" fill="#5B4B8A"/>
57
+ <!-- cyan sparkle -->
58
+ <path d="M18 4l0.5 1.5L20 6l-1.5 0.5L18 8l-0.5-1.5L16 6l1.5-0.5z" fill="#00E5FF"/>
59
+ </svg>
60
+ <span style="letter-spacing:0.04em;font-weight:500">phren</span>
61
+ </div>
62
+ <nav class="nav">
63
+ <button class="nav-item active" data-tab="projects">Projects</button>
64
+ <button class="nav-item" data-tab="review">Review</button>
65
+ <button class="nav-item" data-tab="search">Search</button>
66
+ <button class="nav-item" data-tab="graph">Graph</button>
67
+ <button class="nav-item" data-tab="tasks">Tasks</button>
68
+ <button class="nav-item" data-tab="sessions">Sessions</button>
69
+ <button class="nav-item" data-tab="skills">Skills</button>
70
+ <button class="nav-item" data-tab="hooks">Hooks</button>
71
+ <button class="nav-item" data-tab="settings">Settings</button>
72
+ </nav>
73
+ <span class="status-led status-led-ok" id="sync-led" title="phren is synced"></span>
74
+ <button id="theme-toggle" title="Toggle dark mode" style="margin-left:auto;background:none;border:none;cursor:pointer;padding:8px;border-radius:6px;color:var(--muted);font-size:var(--text-md);line-height:1;transition:color .15s" aria-label="Toggle dark mode">☀️</button>
75
+ <button id="cmdpal-open-btn" title="Search projects (⌘K)" style="background:none;border:1px solid var(--border);cursor:pointer;padding:4px 12px;border-radius:6px;color:var(--muted);font-size:var(--text-sm);font-family:var(--font);transition:color .15s,border-color .15s" class="cmdpal-trigger">⌘K</button>
76
+ </div>
77
+
78
+ <div class="main">
79
+ <!-- ── Projects Tab ──────────────────────────────────────── -->
80
+ <div id="tab-projects" class="tab-content active">
81
+ <input type="text" id="projects-search" placeholder="Search projects..." class="projects-search" />
82
+ <div class="projects-grid" id="projects-grid">
83
+ <div style="padding:40px;color:var(--muted);grid-column:1/-1;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.5"/><path d="M12 15l1-1 1 1-1 1z" fill="#2D2255"/><path d="M18 15l1-1 1 1-1 1z" fill="#2D2255"/><path d="M14 18c0.7 0.5 1.6 0.6 2.6 0.1" stroke="#2D2255" stroke-width="0.6" fill="none"/></svg>Loading projects...</div>
84
+ </div>
85
+ <div id="project-detail-area"></div>
86
+ </div>
87
+
88
+ <!-- ── Review Tab ────────────────────────────────────────── -->
89
+ <div id="tab-review" class="tab-content">
90
+ <div class="card" style="margin-bottom:16px">
91
+ <div class="card-header"><h2>Sync State</h2></div>
92
+ <div class="card-body">
93
+ <div id="sync-state-summary" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;font-size:var(--text-base)">
94
+ <div><strong>Auto-save</strong><div class="text-muted">${h(sync.autoSaveStatus || "n/a")}</div></div>
95
+ <div><strong>Last pull</strong><div class="text-muted">${h(sync.lastPullStatus || "n/a")} ${h(sync.lastPullAt || "")}</div></div>
96
+ <div><strong>Last push</strong><div class="text-muted">${h(sync.lastPushStatus || "n/a")} ${h(sync.lastPushAt || "")}</div></div>
97
+ <div><strong>Unsynced commits</strong><div class="text-muted">${h(String(sync.unsyncedCommits || 0))}</div></div>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ <details class="review-help" style="margin-bottom:16px">
102
+ <summary>Help: How the Review Queue works</summary>
103
+ <dl>
104
+ <dt>What is the Review Queue?</dt>
105
+ <dd>Fragments flagged by governance for human review. Items accumulate here when <code>phren maintain govern</code> is run.</dd>
106
+ <dt>What does Approve do?</dt>
107
+ <dd>Keeps the memory and marks it as reviewed. It stays in your project findings.</dd>
108
+ <dt>What does Reject do?</dt>
109
+ <dd>Permanently removes the memory from your project.</dd>
110
+ <dt>Is this automatic?</dt>
111
+ <dd>No. Agents do not auto-approve. You review each item manually.</dd>
112
+ <dt>How do items get here?</dt>
113
+ <dd><code>phren maintain govern</code> flags stale or low-confidence fragments for review.</dd>
114
+ <dt>How to clear the queue faster?</dt>
115
+ <dd>Run <code>phren maintain prune</code> to auto-remove expired items without manual review.</dd>
116
+ </dl>
117
+ </details>
118
+
119
+ <p style="font-size:var(--text-sm);color:var(--muted);margin-bottom:12px;letter-spacing:-0.01em">Fragments flagged for review. Approve to keep, reject to discard.</p>
120
+
121
+ <div id="review-summary-banner" style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px;align-items:center"></div>
122
+
123
+ <div class="review-filters" id="review-filters" style="display:none">
124
+ <select id="review-filter-project">
125
+ <option value="">All projects</option>
126
+ </select>
127
+ <select id="review-filter-machine">
128
+ <option value="">All machines</option>
129
+ </select>
130
+ <select id="review-filter-model">
131
+ <option value="">All models</option>
132
+ </select>
133
+ <span id="review-filter-count" class="text-muted" style="font-size:var(--text-sm);margin-left:8px"></span>
134
+ <button class="btn btn-sm" id="highlight-only-btn" style="margin-left:auto">Flagged only</button>
135
+ </div>
136
+
137
+ <div id="review-kbd-hints" style="font-size:var(--text-xs);color:var(--muted);margin-bottom:12px;display:none;gap:16px;flex-wrap:wrap">
138
+ <span><kbd>j</kbd>/<kbd>k</kbd> navigate</span>
139
+ <span><kbd>a</kbd> approve</span>
140
+ <span><kbd>r</kbd> reject</span>
141
+ <span><kbd>e</kbd> edit</span>
142
+ </div>
143
+
144
+ <label class="review-select-all" id="review-select-all" style="display:none">
145
+ <input type="checkbox" id="review-select-all-checkbox" />
146
+ Select all
147
+ </label>
148
+
149
+ <div class="review-cards" id="review-cards-list">
150
+ <div class="review-cards-loading" style="text-align:center;padding:40px;color:var(--muted)">Loading...</div>
151
+ </div>
152
+
153
+ <div class="panes">
154
+ <div class="card">
155
+ <div class="card-header"><h2>Recently Accepted</h2></div>
156
+ <div class="card-body"><ul id="accepted-list"><li style="color:var(--muted)">Loading...</li></ul></div>
157
+ </div>
158
+ <div class="card">
159
+ <div class="card-header"><h2>Recently Used</h2></div>
160
+ <div class="card-body"><ul id="usage-list"><li style="color:var(--muted)">Loading...</li></ul></div>
161
+ </div>
162
+ </div>
163
+ </div>
164
+
165
+ <!-- ── Search Tab ────────────────────────────────────────── -->
166
+ <div id="tab-search" class="tab-content">
167
+ <div style="max-width:720px;margin:0 auto">
168
+ <div style="display:flex;gap:8px;margin-bottom:16px">
169
+ <input type="text" id="search-query" placeholder="Search fragments, findings, tasks..." style="flex:1;border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px 12px;background:var(--surface);color:var(--ink);font-size:var(--text-base);font-family:var(--font);outline:none" />
170
+ <select id="search-project-filter" style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:6px 10px;background:var(--surface);color:var(--ink);font-size:var(--text-sm)">
171
+ <option value="">All projects</option>
172
+ </select>
173
+ <select id="search-type-filter" style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:6px 10px;background:var(--surface);color:var(--ink);font-size:var(--text-sm)">
174
+ <option value="">All types</option>
175
+ <option value="finding">Findings</option>
176
+ <option value="task">Tasks</option>
177
+ <option value="reference">Reference</option>
178
+ <option value="summary">Summaries</option>
179
+ </select>
180
+ <button id="search-btn" style="border:1px solid var(--accent);border-radius:var(--radius-sm);padding:6px 16px;background:var(--accent);color:#fff;font-size:var(--text-sm);cursor:pointer;font-family:var(--font)">Search</button>
181
+ </div>
182
+ <div id="search-status" class="text-muted" style="font-size:var(--text-sm);margin-bottom:12px"></div>
183
+ <div id="search-results">
184
+ <div style="padding:40px;color:var(--muted);text-align:center">Enter a query to search across all your fragments and findings.</div>
185
+ </div>
186
+ </div>
187
+ </div>
188
+
189
+ <!-- ── Graph Tab ─────────────────────────────────────────── -->
190
+ <div id="tab-graph" class="tab-content">
191
+ <div class="graph-container">
192
+ <canvas id="graph-canvas"></canvas>
193
+ <div class="graph-tooltip" id="graph-tooltip"></div>
194
+ <div class="graph-controls">
195
+ <button id="graph-zoom-in" title="Zoom in">+</button>
196
+ <button id="graph-zoom-out" title="Zoom out">-</button>
197
+ <button id="graph-reset" title="Reset view">R</button>
198
+ </div>
199
+ <div class="graph-filters">
200
+ <div class="graph-filter" id="graph-filter"></div>
201
+ <div class="graph-filter" id="graph-project-filter"></div>
202
+ <div class="graph-filter" id="graph-limit-row" style="align-items:center;gap:8px"></div>
203
+ </div>
204
+ <!-- legend removed: colors explained in Filters dropdown -->
205
+ </div>
206
+ <div id="graph-detail-panel" class="card" style="margin-top:16px">
207
+ <div class="card-header">
208
+ <h2>Selected Bubble</h2>
209
+ <span id="graph-detail-meta" class="text-muted" style="font-size:var(--text-sm)">Click a bubble to inspect it.</span>
210
+ </div>
211
+ <div class="card-body" id="graph-detail-body" style="display:flex;flex-direction:column;gap:12px">
212
+ <p class="text-muted" style="margin:0">Use the graph filters, then click a project or finding bubble to pin its details here.</p>
213
+ </div>
214
+ </div>
215
+ </div>
216
+
217
+ <!-- ── Skills Tab ────────────────────────────────────────── -->
218
+ <div id="tab-skills" class="tab-content">
219
+ <div class="split-view">
220
+ <div class="split-sidebar" id="skills-list">
221
+ <div style="padding:20px;color:var(--muted)">Loading...</div>
222
+ </div>
223
+ <div class="split-reader" id="skills-reader">
224
+ <div class="reader-empty">Select a skill to view its contents.</div>
225
+ </div>
226
+ </div>
227
+ </div>
228
+
229
+ <!-- ── Hooks Tab ─────────────────────────────────────────── -->
230
+ <div id="tab-hooks" class="tab-content">
231
+ <div class="split-view">
232
+ <div class="split-sidebar" id="hooks-list">
233
+ <div style="padding:20px;color:var(--muted)">Loading...</div>
234
+ </div>
235
+ <div class="split-reader" id="hooks-reader">
236
+ <div class="reader-empty">Select a hook config to view its contents.</div>
237
+ </div>
238
+ </div>
239
+ </div>
240
+
241
+ <!-- ── Tasks Tab ─────────────────────────────────────────── -->
242
+ <div id="tab-tasks" class="tab-content">
243
+ <div class="task-toolbar">
244
+ <select id="tasks-filter-project" class="task-filter-select">
245
+ <option value="">All projects</option>
246
+ </select>
247
+ <select id="tasks-filter-section" class="task-filter-select">
248
+ <option value="">Active + Queue</option>
249
+ <option value="Active">Active only</option>
250
+ <option value="Queue">Queue only</option>
251
+ <option value="Done">Completed</option>
252
+ </select>
253
+ <span id="tasks-count" class="task-count-label"></span>
254
+ </div>
255
+ <div id="tasks-list">
256
+ <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)">Loading tasks...</div></div>
257
+ </div>
258
+ </div>
259
+
260
+ <!-- ── Sessions Tab ──────────────────────────────────────── -->
261
+ <div id="tab-sessions" class="tab-content">
262
+ <div style="display:flex;align-items:center;gap:12px;margin-bottom:16px">
263
+ <select id="sessions-filter-project" style="border:1px solid var(--border);border-radius:var(--radius-sm);padding:6px 10px;background:var(--surface);color:var(--ink);font-size:var(--text-sm)">
264
+ <option value="">All projects</option>
265
+ </select>
266
+ <span id="sessions-count" class="text-muted" style="font-size:var(--text-sm);margin-left:auto"></span>
267
+ </div>
268
+ <div id="sessions-list">
269
+ <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.5"/><path d="M12 15l1-1 1 1-1 1z" fill="#2D2255"/><path d="M18 15l1-1 1 1-1 1z" fill="#2D2255"/><path d="M14 18c0.7 0.5 1.6 0.6 2.6 0.1" stroke="#2D2255" stroke-width="0.6" fill="none"/></svg>Loading sessions...</div>
270
+ </div>
271
+ <div id="session-detail" style="display:none"></div>
272
+ </div>
273
+
274
+ <!-- ── Settings Tab ───────────────────────────────────────── -->
275
+ <div id="tab-settings" class="tab-content">
276
+ <div class="settings-shell">
277
+ <div id="settings-status-inline" class="settings-status-inline" aria-live="polite"></div>
278
+ <section class="settings-section settings-section-findings">
279
+ <div class="settings-section-header">Findings</div>
280
+ <div class="settings-section-body">
281
+ <div id="settings-findings" style="color:var(--muted)">Loading...</div>
282
+ </div>
283
+ </section>
284
+ <section class="settings-section settings-section-behavior">
285
+ <div class="settings-section-header">Behavior</div>
286
+ <div class="settings-section-body">
287
+ <div id="settings-behavior" style="color:var(--muted)">Loading...</div>
288
+ </div>
289
+ </section>
290
+ <section class="settings-section settings-section-integrations">
291
+ <div class="settings-section-header">Integrations</div>
292
+ <div class="settings-section-body">
293
+ <div id="settings-integrations" style="color:var(--muted)">Loading...</div>
294
+ </div>
295
+ </section>
296
+ </div>
297
+ </div>
298
+ </div>
299
+
300
+ <div class="batch-bar" id="batch-bar">
301
+ <span class="batch-bar-count" id="batch-count">0 selected</span>
302
+ <button class="btn btn-sm btn-approve" id="batch-approve-btn">Approve All</button>
303
+ <button class="btn btn-sm btn-reject" id="batch-reject-btn">Reject All</button>
304
+ <select class="btn btn-sm" id="batch-tag-select" style="cursor:pointer">
305
+ <option value="">Approve by tag...</option>
306
+ <option value="decision">[decision]</option>
307
+ <option value="pitfall">[pitfall]</option>
308
+ <option value="pattern">[pattern]</option>
309
+ <option value="fix">[fix]</option>
310
+ <option value="warning">[warning]</option>
311
+ </select>
312
+ <button class="btn btn-sm" id="batch-cancel-btn">Cancel</button>
313
+ </div>
314
+
315
+ <div class="toast-container" id="toast-container"></div>
316
+
317
+ <div class="cmdpal-overlay" id="cmdpal">
318
+ <div class="cmdpal-box" id="cmdpal-box">
319
+ <input class="cmdpal-input" id="cmdpal-input" placeholder="Search projects..." autocomplete="off" />
320
+ <div class="cmdpal-results" id="cmdpal-results"></div>
321
+ </div>
322
+ </div>
323
+
324
+ <script${nonceAttr}>
325
+ ${renderWebUiScript(h(authToken || ""))}
326
+ </script>
327
+ <script${nonceAttr}>
328
+ ${renderGraphScript()}
329
+ </script>
330
+ <script${nonceAttr}>
331
+ ${renderReviewQueueEditSyncScript()}
332
+ </script>
333
+ <script${nonceAttr}>
334
+ ${renderSkillUiEnhancementScript(h(authToken || ""))}
335
+ </script>
336
+ <script${nonceAttr}>
337
+ ${renderProjectReferenceEnhancementScript(h(authToken || ""))}
338
+ </script>
339
+ <script${nonceAttr}>
340
+ ${renderTasksAndSettingsScript(h(authToken || ""))}
341
+ </script>
342
+ <script${nonceAttr}>
343
+ ${renderSearchScript(h(authToken || ""))}
344
+ </script>
345
+ <script${nonceAttr}>
346
+ ${renderEventWiringScript()}
347
+ </script>
348
+ </body>
349
+ </html>`;
350
+ }
351
+ export function renderPageForTests(phrenPath, _csrfToken, authToken) {
352
+ return renderWebUiPage(phrenPath, authToken, "test-nonce");
353
+ }