Step 6c: extract Diagnostics + Profiles + Rules Editors from options.js

Final options.js split. Three new files:

  options-diagnostics.js     245 lines
  options-profiles.js        265 lines
  options-rules-editors.js   328 lines  (adapters + ID normalizers
                                          + custom part detectors)

options.js: 1852 → 1014 lines (838 extracted, ~45% reduction).

Script-tag order in options.html now (load order matters for
top-level let bindings shared across files, e.g. _configuredScanRoots):

  options-cache.js
  options-dupe-review.js
  options-library-issues.js
  options-diagnostics.js
  options-profiles.js
  options-rules-editors.js
  options.js  (entry: IIFE bottom, escapeHtml, overlay previews,
               element picker, paths)

The picker, overlay-preview, and no-match overlay code stays in
options.js — those are tightly intertwined with multiple settings
panes and not worth further splitting today.

node --check passes on each file individually and on the concatenated
load-order stream. Line count of concat (3144) matches the pre-split
sum exactly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
admin
2026-05-23 11:17:55 +02:00
parent 5e79c69d0c
commit d0a2def788
5 changed files with 841 additions and 838 deletions
+245
View File
@@ -0,0 +1,245 @@
// ---------- diagnostics ----------
// Extension ID display + copy button (added when Transfer Assistant was deleted).
// Diagnostics is the canonical home for "what's my extension ID?" info now.
(() => {
const idEl = document.getElementById("diag-extension-id");
const copyBtn = document.getElementById("diag-copy-extension-id");
if (idEl) idEl.textContent = chrome.runtime.id;
if (copyBtn) {
copyBtn.addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(chrome.runtime.id);
copyBtn.textContent = "Copied";
setTimeout(() => { copyBtn.textContent = "Copy ID"; }, 1200);
} catch (_) {
copyBtn.textContent = "Copy failed";
setTimeout(() => { copyBtn.textContent = "Copy ID"; }, 1200);
}
});
}
})();
document.getElementById("run-diag").addEventListener("click", (event) =>
keepActionViewport(event.currentTarget, runDiagnostics)
);
document.getElementById("host-status-run").addEventListener("click", (event) =>
keepActionViewport(event.currentTarget, runHostStatus)
);
document.getElementById("host-repair-run").addEventListener("click", (event) =>
keepActionViewport(event.currentTarget, runHostRepair)
);
document.getElementById("host-verify-run").addEventListener("click", (event) =>
keepActionViewport(event.currentTarget, runHostStatus)
);
document.getElementById("run-all-diag").addEventListener("click", (event) =>
keepActionViewport(event.currentTarget, async () => {
clearNativeRepairCard();
const runtime = await runDiagnostics();
if (runtime && runtime.nativeBlocked) {
renderBlockedByNativeIssue(document.getElementById("host-status-results"), "Host registration");
return;
}
await runHostStatus();
})
);
function renderDiagRows(out, checks, emptyLabel) {
out.innerHTML = "";
if (!checks || checks.length === 0) {
out.innerHTML = `<div class="diag-row warn"><span class="icon">!</span><span class="name">${escapeHtml(emptyLabel)}</span><span class="detail">no checks returned</span></div>`;
return;
}
const counts = checks.reduce((acc, c) => {
const status = c.status || "warn";
acc[status] = (acc[status] || 0) + 1;
return acc;
}, {});
const summary = document.createElement("div");
summary.className = "diag-row " + ((counts.fail || 0) ? "fail" : (counts.warn || 0) ? "warn" : "ok");
summary.innerHTML = `<span class="icon">#</span><span class="name">summary</span><span class="detail">${checks.length} checks · ok ${counts.ok || 0} · info ${counts.info || 0} · warn ${counts.warn || 0} · fail ${counts.fail || 0}</span>`;
out.appendChild(summary);
for (const c of checks) {
const row = document.createElement("div");
row.className = "diag-row " + (c.status || "warn");
const status = c.status || "warn";
const icon = status === "ok" ? "✓" : status === "info" ? "i" : status === "warn" ? "!" : "✗";
row.innerHTML = `<span class="icon">${icon}</span><span class="name">${escapeHtml(c.name)}</span><span class="detail">${formatDiagDetail(c.detail || "")}</span>`;
out.appendChild(row);
}
}
function formatDiagDetail(detail) {
const text = String(detail || "");
if (!text) return "";
const shouldCollapse = text.length > 120 || text.includes("\n") || (text.match(/[;|]/g) || []).length > 2;
if (!shouldCollapse) return escapeHtml(text);
const first = text.split(/\r?\n/)[0].slice(0, 110);
return `<details><summary>${escapeHtml(first)}${text.length > first.length ? "…" : ""}</summary><pre>${escapeHtml(text)}</pre></details>`;
}
async function runDiagnostics() {
const out = document.getElementById("diag-results");
clearNativeRepairCard();
out.innerHTML = '<div class="diag-row warn"><span class="icon">…</span><span class="name">running…</span><span class="detail">waiting for native host</span></div>';
try {
const r = await chrome.runtime.sendMessage({ type: "diagnostics" });
if (!r || !r.ok) {
await renderNativeMessagingFailure(r);
renderBlockedByNativeIssue(out, "Runtime diagnostics");
return { nativeBlocked: true };
}
renderDiagRows(out, r.checks || [], "runtime");
return { ok: true };
} catch (err) {
out.innerHTML = `<div class="diag-row fail"><span class="icon">✗</span><span class="name">runtime</span><span class="detail">${escapeHtml(err.message || String(err))}</span></div>`;
return { ok: false };
}
}
async function runHostStatus() {
const out = document.getElementById("host-status-results");
clearNativeRepairCard();
out.innerHTML = '<div class="diag-row warn"><span class="icon">…</span><span class="name">checking…</span><span class="detail">reading manifest and registry state</span></div>';
try {
const r = await chrome.runtime.sendMessage({ type: "host-status" });
if (!r || !r.ok) {
await renderNativeMessagingFailure(r);
renderBlockedByNativeIssue(out, "Native host checks");
return { nativeBlocked: true };
}
renderDiagRows(out, r.checks || [], "host status");
return { ok: true };
} catch (err) {
out.innerHTML = `<div class="diag-row fail"><span class="icon">✗</span><span class="name">host status</span><span class="detail">${escapeHtml(err.message || String(err))}</span></div>`;
return { ok: false };
}
}
async function runHostRepair() {
const out = document.getElementById("host-status-results");
clearNativeRepairCard();
out.innerHTML = '<div class="diag-row warn"><span class="icon">…</span><span class="name">repairing…</span><span class="detail">updating reachable native host manifest and user registration</span></div>';
try {
const r = await chrome.runtime.sendMessage({ type: "repair-host" });
if (!r || !r.ok) {
if (r?.error_kind) {
await renderNativeMessagingFailure(r);
renderBlockedByNativeIssue(out, "Registration repair");
} else {
out.innerHTML = `<div class="diag-row fail"><span class="icon">✗</span><span class="name">Registration repair</span><span class="detail">${escapeHtml(r?.error || "repair failed")}</span></div>`;
}
return { ok: false };
}
const checks = r.verification?.checks || [];
renderDiagRows(out, checks, "repair verification");
renderCompletedNativeRepair(r);
return { ok: true };
} catch (err) {
out.innerHTML = `<div class="diag-row fail"><span class="icon">✗</span><span class="name">Registration repair</span><span class="detail">${escapeHtml(err.message || String(err))}</span></div>`;
return { ok: false };
}
}
function clearNativeRepairCard() {
const card = document.getElementById("native-repair-card");
const out = document.getElementById("native-repair-results");
const title = document.getElementById("native-repair-title");
if (card) card.style.display = "none";
if (out) out.innerHTML = "";
if (title) title.textContent = "Native host setup";
}
function renderCompletedNativeRepair(response) {
const card = document.getElementById("native-repair-card");
const out = document.getElementById("native-repair-results");
if (!card || !out) return;
card.style.display = "";
const title = document.getElementById("native-repair-title");
if (title) title.textContent = "Registration repair completed";
const regs = (response.registrations || []).filter((x) => x.status === "ok").length;
out.innerHTML = `
<div class="diag-row ok"><span class="icon">✓</span><span class="name">Repair applied</span><span class="detail">${escapeHtml(response.message || "native host registration repaired")}</span></div>
<div class="diag-row info"><span class="icon">i</span><span class="name">Manifest</span><span class="detail">${escapeHtml(response.manifest_path || "")}</span></div>
<div class="diag-row info"><span class="icon">i</span><span class="name">User registry</span><span class="detail">${escapeHtml(`${regs} HKCU registration entr${regs === 1 ? "y" : "ies"} updated`)}</span></div>
<div class="diag-row warn"><span class="icon">!</span><span class="name">Restart required</span><span class="detail">Fully close Brave, reopen it, reload the extension, then click Verify Registration. If Brave still blocks the host, run the registration steps shown by Diagnostics.</span></div>
`;
}
function renderBlockedByNativeIssue(out, title) {
out.innerHTML = `<div class="diag-row info"><span class="icon">i</span><span class="name">${escapeHtml(title)}</span><span class="detail">Blocked until this PC registers the native host for the current extension ID. Use the setup card above.</span></div>`;
}
async function getPackagedHostPaths() {
try {
const resp = await fetch(chrome.runtime.getURL("host/com.rcjav.host.json"));
if (!resp.ok) return {};
const manifest = await resp.json();
const bat = manifest.path || "";
const hostDir = bat.replace(/[\\/][^\\/]+$/, "");
return {
hostBat: bat,
hostDir,
registerBat: hostDir ? hostDir + "\\register-host.bat" : "",
installPs1: hostDir ? hostDir + "\\install-host.ps1" : "",
};
} catch {
return {};
}
}
async function renderNativeMessagingFailure(response) {
const card = document.getElementById("native-repair-card");
const out = document.getElementById("native-repair-results");
if (!card || !out) return;
card.style.display = "";
const title = document.getElementById("native-repair-title");
if (title) title.textContent = "Register host on this PC";
const error = response?.error || "no response";
const kind = response?.error_kind || (/forbidden/i.test(error) ? "forbidden" : "unknown");
const extensionId = response?.extension_id || chrome.runtime.id;
const paths = await getPackagedHostPaths();
const installCommand = paths.installPs1
? `pwsh -ExecutionPolicy Bypass -File "${paths.installPs1}" -ExtensionId ${extensionId}`
: `pwsh -ExecutionPolicy Bypass -File ".\\host\\install-host.ps1" -ExtensionId ${extensionId}`;
const registerCommand = paths.registerBat ? `"${paths.registerBat}"` : ".\\host\\register-host.bat";
let cause = "This extension cannot launch the native messaging host yet.";
let fix = "Register the host for this extension ID, fully restart Brave, then verify registration.";
if (kind === "forbidden") {
cause = "Brave found the native host, but this extension ID is not allowed to launch it on this PC.";
fix = "This usually happens after loading the extension on another PC or under a different extension ID.";
} else if (kind === "not_found") {
cause = "Brave could not find a registered native messaging host for com.rcjav.host on this PC.";
fix = "Run the registration script from the extension host folder.";
} else if (kind === "disconnected") {
cause = "The native host started and then disconnected or crashed.";
fix = "After registration is fixed, run Runtime diagnostics again to check Python, rc-jav, and rclone.";
} else if (kind === "timeout") {
cause = "The native host did not respond before the timeout.";
fix = "Restart Brave and check whether a scan or rclone command is stuck.";
}
out.innerHTML = `
<div class="diag-row warn"><span class="icon">!</span><span class="name">Setup required</span><span class="detail">Native host registration must be fixed before cache, runtime, and host checks can run.</span></div>
<div class="diag-row warn"><span class="icon">!</span><span class="name">Likely cause</span><span class="detail">${escapeHtml(cause)}</span></div>
<div class="diag-row info"><span class="icon">i</span><span class="name">Host message</span><span class="detail">${escapeHtml(error)}</span></div>
<div class="diag-row ok"><span class="icon">→</span><span class="name">Fix on this PC</span><span class="detail">${escapeHtml(fix)}</span></div>
<div class="diag-row info"><span class="icon">i</span><span class="name">Extension ID</span><span class="detail">${escapeHtml(extensionId)}</span></div>
<div class="diag-row info"><span class="icon">1</span><span class="name">Run register-host</span><span class="detail">
<details open><summary>${escapeHtml(registerCommand)}</summary><pre>${escapeHtml(`Run ${registerCommand}\nWhen it asks for the extension ID, enter:\n${extensionId}\n\nPowerShell alternative:\n${installCommand}`)}</pre></details>
<span class="diag-action"><button type="button" data-copy="${escapeHtml(registerCommand)}" data-copy-label="Copy Script Path">Copy Script Path</button><button type="button" data-copy="${escapeHtml(extensionId)}" data-copy-label="Copy Extension ID">Copy Extension ID</button><button type="button" data-copy="${escapeHtml(installCommand)}" data-copy-label="Copy PowerShell Alternative">Copy PowerShell Alternative</button></span>
</span></div>
<div class="diag-row info"><span class="icon">2</span><span class="name">Restart Brave</span><span class="detail">Close every Brave window/process, reopen Brave, then reload the extension.</span></div>
<div class="diag-row info"><span class="icon">3</span><span class="name">Verify</span><span class="detail"><span class="diag-action"><button type="button" data-verify-registration>Verify Registration</button></span></span></div>
`;
for (const btn of out.querySelectorAll("button[data-copy]")) {
btn.addEventListener("click", async () => {
await navigator.clipboard.writeText(btn.dataset.copy || "");
btn.textContent = "Copied";
setTimeout(() => { btn.textContent = btn.dataset.copyLabel || "Copy"; }, 1200);
});
}
for (const btn of out.querySelectorAll("button[data-verify-registration]")) {
btn.addEventListener("click", runHostStatus);
}
}