UNPKG

@phren/cli

Version:

Knowledge layer for AI agents - CLI, MCP server, and data layer

449 lines (432 loc) • 28.1 kB
import { moduleEnabled } from "../modules/runtime.js"; import { WEB_UI_STYLES, renderWebUiScript } from "./assets.js"; import { renderGraphScript } from "./graph.js"; import { FINDING_CAPTURE_UI_STYLES, LAYOUT_VIEWPORT_STYLES, NOTES_UI_STYLES, PROJECT_REFERENCE_UI_STYLES, REVIEW_UI_STYLES, SETTINGS_TAB_UI_STYLES, TASK_UI_STYLES } from "./styles.js"; import { PHREN_DEEP_VOID_STYLES } from "./deep-void.js"; import { GRAPH_HUD_STYLES } from "./graph-chrome.js"; import { renderSharedWebUiHelpers, renderSkillUiEnhancementScript, renderProfileSwitcherScript, renderFindingCaptureEnhancementScript, renderNotesEnhancementScript, renderProjectReferenceEnhancementScript, renderTasksAndSettingsScript, renderReviewQueueKeyboardScript, renderSearchScript, renderEventWiringScript, renderGraphHostScript, renderActivityStreamScript, renderGraphWalkScript, } from "./scripts.js"; function h(s) { return s .replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/"/g, "&quot;"); } export function renderWebUiPage(_phrenPath, authToken, nonce) { const tasksEnabled = moduleEnabled(_phrenPath, "tasks"); const nonceAttr = nonce ? ` nonce="${h(nonce)}"` : ""; return `<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="preconnect" href="https://fonts.bunny.net" /> <link href="https://fonts.bunny.net/css?family=inter:400,500,600,700|space-grotesk:400,500,600,700|jetbrains-mono:400,500,700&display=swap" rel="stylesheet" /> <title>phren</title> <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ccircle cx='16' cy='16' r='15' fill='%2312122a'/%3E%3Ccircle cx='16' cy='16' r='8' fill='%237c6bf5'/%3E%3Ccircle cx='13' cy='13' r='3' fill='%23a99cfa'/%3E%3C/svg%3E" /> <style> ${WEB_UI_STYLES} ${PROJECT_REFERENCE_UI_STYLES} ${SETTINGS_TAB_UI_STYLES} ${TASK_UI_STYLES} ${REVIEW_UI_STYLES} ${PHREN_DEEP_VOID_STYLES} ${LAYOUT_VIEWPORT_STYLES} ${GRAPH_HUD_STYLES} ${FINDING_CAPTURE_UI_STYLES} ${NOTES_UI_STYLES} </style> </head> <body> <div class="header"> <div class="header-brand"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="24" height="24" style="image-rendering:pixelated"> <circle cx="32" cy="32" r="32" fill="#12122a"/> <g fill="rgb(103,226,248)"><rect x="44" y="12" width="4" height="4"/></g> <g fill="rgb(138,127,236)"><rect x="20" y="16" width="4" height="4"/></g> <g fill="rgb(150,144,247)"><rect x="24" y="16" width="4" height="4"/></g> <g fill="rgb(102,74,242)"><rect x="28" y="16" width="4" height="4"/></g> <g fill="rgb(168,165,249)"><rect x="32" y="16" width="4" height="4"/></g> <g fill="rgb(144,147,247)"><rect x="36" y="16" width="4" height="4"/></g> <g fill="rgb(119,108,251)"><rect x="40" y="16" width="4" height="4"/></g> <g fill="rgb(102,80,247)"><rect x="20" y="20" width="4" height="4"/></g> <g fill="rgb(105,77,249)"><rect x="24" y="20" width="4" height="4"/></g> <g fill="rgb(197,195,252)"><rect x="28" y="20" width="4" height="4"/></g> <g fill="rgb(154,148,247)"><rect x="32" y="20" width="4" height="4"/></g> <g fill="rgb(152,131,250)"><rect x="36" y="20" width="4" height="4"/></g> <g fill="rgb(151,145,249)"><rect x="40" y="20" width="4" height="4"/></g> <g fill="rgb(37,37,143)"><rect x="44" y="20" width="4" height="4"/></g> <g fill="rgb(84,63,227)"><rect x="16" y="24" width="4" height="4"/></g> <g fill="rgb(153,132,251)"><rect x="20" y="24" width="4" height="4"/></g> <g fill="rgb(154,143,249)"><rect x="24" y="24" width="4" height="4"/></g> <g fill="rgb(159,147,249)"><rect x="28" y="24" width="4" height="4"/></g> <g fill="rgb(93,76,236)"><rect x="32" y="24" width="4" height="4"/></g> <g fill="rgb(156,134,251)"><rect x="36" y="24" width="4" height="4"/></g> <g fill="rgb(154,137,248)"><rect x="40" y="24" width="4" height="4"/></g> <g fill="rgb(149,130,251)"><rect x="44" y="24" width="4" height="4"/></g> <g fill="rgb(34,37,124)"><rect x="12" y="28" width="4" height="4"/></g> <g fill="rgb(144,124,251)"><rect x="16" y="28" width="4" height="4"/></g> <g fill="rgb(150,132,250)"><rect x="20" y="28" width="4" height="4"/></g> <g fill="rgb(149,130,250)"><rect x="24" y="28" width="4" height="4"/></g> <g fill="rgb(153,137,252)"><rect x="28" y="28" width="4" height="4"/></g> <g fill="rgb(154,136,250)"><rect x="32" y="28" width="4" height="4"/></g> <g fill="rgb(154,138,250)"><rect x="36" y="28" width="4" height="4"/></g> <g fill="rgb(157,141,249)"><rect x="40" y="28" width="4" height="4"/></g> <g fill="rgb(82,62,230)"><rect x="44" y="28" width="4" height="4"/></g> <g fill="rgb(29,34,113)"><rect x="12" y="32" width="4" height="4"/></g> <g fill="rgb(21,32,104)"><rect x="16" y="32" width="4" height="4"/></g> <g fill="rgb(146,127,251)"><rect x="20" y="32" width="4" height="4"/></g> <g fill="rgb(157,145,248)"><rect x="24" y="32" width="4" height="4"/></g> <g fill="rgb(20,31,101)"><rect x="28" y="32" width="4" height="4"/></g> <g fill="rgb(152,138,249)"><rect x="32" y="32" width="4" height="4"/></g> <g fill="rgb(154,141,248)"><rect x="36" y="32" width="4" height="4"/></g> <g fill="rgb(78,62,218)"><rect x="40" y="32" width="4" height="4"/></g> <g fill="rgb(124,107,250)"><rect x="44" y="32" width="4" height="4"/></g> <g fill="rgb(38,41,148)"><rect x="48" y="32" width="4" height="4"/></g> <g fill="rgb(147,129,252)"><rect x="16" y="36" width="4" height="4"/></g> <g fill="rgb(147,125,251)"><rect x="20" y="36" width="4" height="4"/></g> <g fill="rgb(147,127,251)"><rect x="24" y="36" width="4" height="4"/></g> <g fill="rgb(149,131,251)"><rect x="28" y="36" width="4" height="4"/></g> <g fill="rgb(120,104,251)"><rect x="32" y="36" width="4" height="4"/></g> <g fill="rgb(121,109,252)"><rect x="36" y="36" width="4" height="4"/></g> <g fill="rgb(109,90,247)"><rect x="40" y="36" width="4" height="4"/></g> <g fill="rgb(113,92,249)"><rect x="44" y="36" width="4" height="4"/></g> <g fill="rgb(97,88,247)"><rect x="20" y="40" width="4" height="4"/></g> <g fill="rgb(96,85,243)"><rect x="24" y="40" width="4" height="4"/></g> <g fill="rgb(71,58,216)"><rect x="28" y="40" width="4" height="4"/></g> <g fill="rgb(90,71,238)"><rect x="32" y="40" width="4" height="4"/></g> <g fill="rgb(115,103,249)"><rect x="36" y="40" width="4" height="4"/></g> <g fill="rgb(80,61,228)"><rect x="40" y="40" width="4" height="4"/></g> <g fill="rgb(16,18,94)"><rect x="44" y="40" width="4" height="4"/></g> <g fill="rgb(69,52,218)"><rect x="24" y="44" width="4" height="4"/></g> <g fill="rgb(17,28,102)"><rect x="32" y="44" width="4" height="4"/></g> <g fill="rgb(20,31,99)"><rect x="36" y="44" width="4" height="4"/></g> <g fill="rgb(160,163,251)"><rect x="20" y="48" width="4" height="4"/></g> <g fill="rgb(34,34,140)"><rect x="24" y="48" width="4" height="4"/></g> <g fill="rgb(159,159,250)"><rect x="28" y="48" width="4" height="4"/></g> <g fill="rgb(26,36,133)"><rect x="32" y="48" width="4" height="4"/></g> <g fill="rgb(26,41,132)"><rect x="36" y="48" width="4" height="4"/></g> <g fill="rgb(159,165,249)"><rect x="40" y="48" width="4" height="4"/></g> </svg> <span style="letter-spacing:0.04em;font-weight:500">phren</span> </div> <nav class="nav"> <button class="nav-item active" data-tab="projects">Projects</button> <button class="nav-item" data-tab="review">Review</button> <button class="nav-item" data-tab="activity">Activity</button> <button class="nav-item" data-tab="search">Search</button> <button class="nav-item" data-tab="graph">Graph</button> ${tasksEnabled ? '<button class="nav-item" data-tab="tasks">Tasks</button>' : ""} <button class="nav-item" data-tab="skills">Skills</button> <button class="nav-item" data-tab="hooks">Hooks</button> <button class="nav-item" data-tab="settings">Settings</button> </nav> <span class="status-led status-led-ok" id="sync-led" title="phren is synced"></span> <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> <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> </div> <div class="main"> <!-- ── Projects Tab ──────────────────────────────────────── --> <div id="tab-projects" class="tab-content active"> <input type="text" id="projects-search" placeholder="Search projects..." class="projects-search" /> <div class="projects-grid" id="projects-grid"> <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> </div> <div id="project-detail-area"></div> </div> <!-- ── Review Tab ────────────────────────────────────────── --> <div id="tab-review" class="tab-content"> <div class="review-toolbar" id="review-filters" style="display:none"> <select id="review-filter-project"> <option value="">All projects</option> </select> <label class="review-flagged-toggle" id="review-flagged-toggle"> <input type="checkbox" id="highlight-only-btn" /> <span>Flagged only</span> </label> <span id="review-filter-count" class="text-muted" style="font-size:var(--text-sm);margin-left:auto"></span> <label id="review-select-all" style="display:none;align-items:center;gap:6px;font-size:var(--text-sm);color:var(--muted);cursor:pointer;user-select:none"> <input type="checkbox" id="review-select-all-cb" style="width:14px;height:14px;cursor:pointer;accent-color:var(--accent)" /> Select all </label> <span id="review-sync-status" class="review-sync-dot" title="Sync status"> <span class="review-sync-indicator" id="review-sync-indicator"></span> </span> </div> <div id="batch-bar" class="batch-bar"> <span id="batch-count" class="batch-bar-count"></span> <button class="btn btn-sm btn-approve" data-batch-action="approve">Approve selected</button> <button class="btn btn-sm btn-reject" data-batch-action="reject">Reject selected</button> <button class="btn btn-sm" data-batch-action="clear">Clear</button> </div> <div class="review-cards" id="review-cards-list"> <div class="review-cards-loading" style="text-align:center;padding:40px;color:var(--muted)">Loading...</div> </div> <div class="panes"> <div class="card"> <div class="card-header"><h2>Recently Accepted</h2></div> <div class="card-body"><ul id="accepted-list"><li style="color:var(--muted)">Loading...</li></ul></div> </div> <div class="card"> <div class="card-header"><h2>Recently Used</h2></div> <div class="card-body"><ul id="usage-list"><li style="color:var(--muted)">Loading...</li></ul></div> </div> </div> </div> <!-- ── Activity Tab ──────────────────────────────────────── --> <div id="tab-activity" class="tab-content"> <style> .activity-led{display:inline-block;width:10px;height:10px;border-radius:50%;background:var(--muted);flex:0 0 auto} .activity-led-ok{background:var(--success);box-shadow:0 0 0 0 var(--success-dim);animation:activity-pulse 2s infinite} .activity-led-warn{background:var(--warning)} .activity-led-err{background:var(--danger)} @keyframes activity-pulse{0%{box-shadow:0 0 0 0 var(--success-glow,rgba(16,185,129,.35))}70%{box-shadow:0 0 0 7px rgba(16,185,129,0)}100%{box-shadow:0 0 0 0 rgba(16,185,129,0)}} .activity-feed{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px} .activity-item{border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px 12px;background:var(--surface);transition:background .6s ease,border-color .6s ease} .activity-item-new{background:var(--accent-dim);border-color:var(--accent)} .activity-row{display:flex;align-items:center;gap:8px} .activity-type{font-size:var(--text-xs);text-transform:uppercase;letter-spacing:.04em;color:var(--accent);border:1px solid var(--border);border-radius:999px;padding:1px 8px;flex:0 0 auto} .activity-loc{font-family:var(--mono);font-size:var(--text-sm);color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .activity-time{margin-left:auto;font-size:var(--text-xs);color:var(--muted);flex:0 0 auto} .activity-query{font-size:var(--text-sm);color:var(--ink-secondary);margin-top:4px} .activity-snippet{font-size:var(--text-sm);color:var(--muted);margin-top:4px;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical} </style> <div style="max-width:860px;margin:0 auto"> <div style="display:flex;align-items:center;gap:10px;margin-bottom:6px"> <span id="activity-led" class="activity-led" title="Live status"></span> <h2 style="font-size:var(--text-lg);font-weight:600;margin:0">Live Lookups</h2> <span id="activity-status" class="text-muted" style="font-size:var(--text-sm);margin-left:auto">Connecting…</span> </div> <p class="text-muted" style="font-size:var(--text-sm);margin:0 0 16px 0">Memories phren lands on as it searches, streamed in real time.</p> <ul id="activity-feed" class="activity-feed"><li style="color:var(--muted);list-style:none">Waiting for lookups…</li></ul> </div> </div> <!-- ── Search Tab ────────────────────────────────────────── --> <div id="tab-search" class="tab-content"> <div style="max-width:720px;margin:0 auto"> <div style="display:flex;gap:8px;margin-bottom:16px"> <input type="text" id="search-query" placeholder="${tasksEnabled ? "Search fragments, findings, tasks..." : "Search fragments and findings..."}" 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" /> <div id="search-project-wrap" style="position:relative"> <button id="search-project-btn" type="button" onclick="window._phrenToggleProjectDropdown()" 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);cursor:pointer;font-family:var(--font);min-width:120px;text-align:left;white-space:nowrap">All projects</button> <div id="search-project-dropdown" style="display:none;position:absolute;top:100%;left:0;z-index:50;margin-top:4px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:0 4px 12px rgba(0,0,0,.15);max-height:240px;overflow-y:auto;min-width:160px"></div> </div> <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)"> <option value="">All types</option> <option value="finding">Findings</option> ${tasksEnabled ? '<option value="task">Tasks</option>' : ""} <option value="reference">Reference</option> <option value="summary">Summaries</option> </select> <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> </div> <div id="search-status" class="text-muted" style="font-size:var(--text-sm);margin-bottom:12px"></div> <div id="search-results"> <div style="padding:40px;color:var(--muted);text-align:center">Enter a query to search across all your fragments and findings.</div> </div> </div> </div> <!-- ── Graph Tab ─────────────────────────────────────────── --> <div id="tab-graph" class="tab-content"> <div class="graph-container"> <div id="graph-canvas" aria-label="Knowledge graph"></div> <div class="graph-controls"> <button id="graph-zoom-in" title="Zoom in">+</button> <button id="graph-zoom-out" title="Zoom out">-</button> <button id="graph-reset" title="Reset view">R</button> <button id="graph-reset-layout" title="Re-run layout">L</button> </div> <div class="graph-filters"> <div class="graph-filter" id="graph-filter"></div> <div class="graph-filter" id="graph-project-filter"></div> <div class="graph-filter" id="graph-limit-row" style="align-items:center;gap:8px"></div> </div> <div id="graph-node-popover" style="display:none;position:absolute;left:0;top:0;z-index:12;max-width:min(440px,calc(100% - 24px));pointer-events:none"> <div id="graph-node-popover-card" class="card" style="pointer-events:auto;position:relative;box-shadow:var(--shadow-lg);border:1px solid var(--border);background:color-mix(in srgb, var(--surface) 96%, transparent);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px)"> <div id="graph-node-content" style="padding:18px 18px 16px 18px"></div> </div> </div> </div> </div> <!-- ── Skills Tab ────────────────────────────────────────── --> <div id="tab-skills" class="tab-content"> <div class="split-view"> <div class="split-sidebar" id="skills-list"> <div style="padding:20px;color:var(--muted)">Loading...</div> </div> <div class="split-reader" id="skills-reader"> <div class="reader-empty">Select a skill to view its contents.</div> </div> </div> </div> <!-- ── Hooks Tab ─────────────────────────────────────────── --> <div id="tab-hooks" class="tab-content"> <div class="split-view"> <div class="split-sidebar" id="hooks-list"> <div style="padding:20px;color:var(--muted)">Loading...</div> </div> <div class="split-reader" id="hooks-reader"> <div class="reader-empty">Select a hook config to view its contents.<br/><span style="font-size:var(--text-sm);color:var(--muted);margin-top:8px;display:inline-block">Per-project hooks can also be configured in Settings &gt; [project name].</span></div> </div> </div> </div> <!-- ── Tasks Tab ─────────────────────────────────────────── --> <div id="tab-tasks" class="tab-content"> <div class="task-toolbar"> <select id="tasks-filter-project" class="task-filter-select"> <option value="">All projects</option> </select> <select id="tasks-filter-section" class="task-filter-select"> <option value="">Active + Queue</option> <option value="Active">Active only</option> <option value="Queue">Queue only</option> <option value="Done">Completed</option> </select> <select id="tasks-filter-responsibility" class="task-filter-select" aria-label="Task responsibility"><option value="">Human + Agent</option><option value="human">Human</option><option value="agent">Agent</option></select> <span id="tasks-count" class="task-count-label"></span> </div> <div id="tasks-list"> <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)">Loading tasks...</div></div> </div> </div> <!-- ── Settings Tab ───────────────────────────────────────── --> <div id="tab-settings" class="tab-content"> <div class="settings-shell"> <div id="settings-status-inline" class="settings-status-inline" aria-live="polite"></div> <section class="settings-section" style="border-top:3px solid color-mix(in srgb, var(--cyan) 45%, var(--border))"> <div class="settings-section-header" style="display:flex;align-items:center;justify-content:space-between;gap:16px"> <span>Scope</span> <select id="settings-project-select" 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);font-family:var(--font)"> <option value="">Global (all projects)</option> </select> </div> <div class="settings-section-body" style="padding:12px 18px"> <div id="settings-scope-note" style="font-size:var(--text-sm);color:var(--muted)">Showing global settings. Select a project to view and edit per-project overrides.</div> </div> </section> <section class="settings-section" style="border-top:3px solid color-mix(in srgb, var(--blue) 45%, var(--border))"> <div class="settings-section-header">Profile</div> <div class="settings-section-body"> <div style="font-size:var(--text-sm);color:var(--muted);margin-bottom:12px"> <label for="profile-select" style="display:block;margin-bottom:6px;color:var(--ink)">Active Profile:</label> <select id="profile-select" 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);font-family:var(--font);width:100%;max-width:200px"> <option>Loading profiles...</option> </select> </div> <div id="profile-status" style="font-size:var(--text-sm);color:var(--muted)"></div> </div> </section> <section id="settings-project-info-section" class="settings-section" style="display:none;border-top:3px solid color-mix(in srgb, var(--accent) 45%, var(--border))"> <div class="settings-section-header">Project Info</div> <div class="settings-section-body"> <div id="settings-project-info" style="color:var(--muted)"></div> </div> </section> <section class="settings-section settings-section-findings"> <div class="settings-section-header">Findings</div> <div class="settings-section-body"> <div id="settings-findings" style="color:var(--muted)">Loading...</div> </div> </section> <section class="settings-section settings-section-behavior"> <div class="settings-section-header">Behavior</div> <div class="settings-section-body"> <div id="settings-behavior" style="color:var(--muted)">Loading...</div> </div> </section> <section class="settings-section" style="border-top:3px solid color-mix(in srgb, var(--warning) 45%, var(--border))"> <div class="settings-section-header">Retention Policy</div> <div class="settings-section-body"> <div id="settings-retention" style="color:var(--muted)">Loading...</div> </div> </section> <section class="settings-section" style="border-top:3px solid color-mix(in srgb, var(--success) 45%, var(--border))"> <div class="settings-section-header">Workflow Policy</div> <div class="settings-section-body"> <div id="settings-workflow" style="color:var(--muted)">Loading...</div> </div> </section> <section class="settings-section" style="border-top:3px solid color-mix(in srgb, var(--danger, var(--warning)) 45%, var(--border))"> <div class="settings-section-header">Access Control</div> <div class="settings-section-body"> <div id="settings-access" style="color:var(--muted)">Loading...</div> </div> </section> <section class="settings-section" style="border-top:3px solid color-mix(in srgb, var(--cyan) 45%, var(--border))"> <div class="settings-section-header">Index Policy</div> <div class="settings-section-body"> <div id="settings-index" style="color:var(--muted)">Loading...</div> </div> </section> <section class="settings-section settings-section-integrations"> <div class="settings-section-header">Integrations</div> <div class="settings-section-body"> <div id="settings-integrations" style="color:var(--muted)">Loading...</div> </div> </section> <section class="settings-section settings-section-stores"> <div class="settings-section-header">Stores</div> <div class="settings-section-body"> <div id="settings-stores" style="color:var(--muted)">Loading...</div> </div> </section> </div> </div> </div> <div class="toast-container" id="toast-container"></div> <div class="cmdpal-overlay" id="cmdpal"> <div class="cmdpal-box" id="cmdpal-box"> <input class="cmdpal-input" id="cmdpal-input" placeholder="Search projects..." autocomplete="off" /> <div class="cmdpal-results" id="cmdpal-results"></div> </div> </div> <script${nonceAttr}> ${renderWebUiScript(authToken || "")} </script> <script${nonceAttr}> ${renderGraphScript()} </script> <script${nonceAttr}> ${renderGraphHostScript()} </script> <script${nonceAttr}> ${renderSharedWebUiHelpers(authToken || "")} </script> <script${nonceAttr}> ${renderFindingCaptureEnhancementScript()} </script> <script${nonceAttr}> ${renderNotesEnhancementScript()} </script> <script${nonceAttr}> ${renderSkillUiEnhancementScript(h(authToken || ""))} </script> <script${nonceAttr}> ${renderProfileSwitcherScript(h(authToken || ""))} </script> <script${nonceAttr}> ${renderProjectReferenceEnhancementScript(h(authToken || ""))} </script> <script${nonceAttr}> ${renderTasksAndSettingsScript(authToken || "")} </script> <script${nonceAttr}> ${renderReviewQueueKeyboardScript(authToken || "")} </script> <script${nonceAttr}> ${renderActivityStreamScript(authToken || "")} </script> <script${nonceAttr}> ${renderGraphWalkScript()} </script> <script${nonceAttr}> ${renderSearchScript(authToken || "")} </script> <script${nonceAttr}> ${renderEventWiringScript()} </script> </body> </html>`; } export function renderPageForTests(phrenPath, _csrfToken, authToken) { return renderWebUiPage(phrenPath, authToken, "test-nonce"); }