perf: reduce renderer and repository polling work
ForgeFlow quality gate / quality (push) Canceled after 0s
ForgeFlow quality gate / quality (push) Canceled after 0s
This commit is contained in:
@@ -40,27 +40,32 @@ class RepositoryMonitor {
|
||||
if (this.running || !this.paths.length) return;
|
||||
this.running = true;
|
||||
try {
|
||||
for (const localPath of this.paths) {
|
||||
if (this.paused.has(localPath)) continue;
|
||||
try {
|
||||
const status = await this.git.status(localPath);
|
||||
const next = this.git.statusFingerprint(status);
|
||||
const previous = this.fingerprints.get(localPath);
|
||||
this.fingerprints.set(localPath, next);
|
||||
if (previous && previous !== next) {
|
||||
await this.diagnostics?.debug('repository-monitor.changed', { localPath, head: status.head, branch: status.branch?.head, counts: status.counts });
|
||||
this.onChange?.({ localPath, status, reason: 'working-tree-changed' });
|
||||
}
|
||||
} catch (error) {
|
||||
const next = `error:${error.message}`;
|
||||
const previous = this.fingerprints.get(localPath);
|
||||
this.fingerprints.set(localPath, next);
|
||||
if (previous && previous !== next) {
|
||||
await this.diagnostics?.warning('repository-monitor.unavailable', { localPath, message: error.message });
|
||||
this.onChange?.({ localPath, error: error.message, reason: 'repository-unavailable' });
|
||||
const queue = [...this.paths];
|
||||
const workers = Array.from({ length: Math.min(4, queue.length) }, async () => {
|
||||
while (queue.length) {
|
||||
const localPath = queue.shift();
|
||||
if (this.paused.has(localPath)) continue;
|
||||
try {
|
||||
const status = await this.git.status(localPath);
|
||||
const next = this.git.statusFingerprint(status);
|
||||
const previous = this.fingerprints.get(localPath);
|
||||
this.fingerprints.set(localPath, next);
|
||||
if (previous && previous !== next) {
|
||||
await this.diagnostics?.debug('repository-monitor.changed', { localPath, head: status.head, branch: status.branch?.head, counts: status.counts });
|
||||
this.onChange?.({ localPath, status, reason: 'working-tree-changed' });
|
||||
}
|
||||
} catch (error) {
|
||||
const next = `error:${error.message}`;
|
||||
const previous = this.fingerprints.get(localPath);
|
||||
this.fingerprints.set(localPath, next);
|
||||
if (previous && previous !== next) {
|
||||
await this.diagnostics?.warning('repository-monitor.unavailable', { localPath, message: error.message });
|
||||
this.onChange?.({ localPath, error: error.message, reason: 'repository-unavailable' });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
await Promise.all(workers);
|
||||
} finally {
|
||||
this.running = false;
|
||||
}
|
||||
|
||||
@@ -173,6 +173,7 @@ const ui = {
|
||||
setupValidation: null,
|
||||
activeDeployment: null,
|
||||
operationPollTimer: null,
|
||||
inputRenderTimer: null,
|
||||
isMock: false,
|
||||
refreshError: null,
|
||||
autoRefreshPending: false,
|
||||
@@ -190,6 +191,14 @@ const ui = {
|
||||
auditEvents: [],
|
||||
};
|
||||
|
||||
function scheduleInputRender(delay = 120) {
|
||||
if (ui.inputRenderTimer) clearTimeout(ui.inputRenderTimer);
|
||||
ui.inputRenderTimer = setTimeout(() => {
|
||||
ui.inputRenderTimer = null;
|
||||
render();
|
||||
}, delay);
|
||||
}
|
||||
|
||||
function selectedRepository() {
|
||||
return (
|
||||
ui.repositories.find(
|
||||
|
||||
+47
-38
@@ -17,20 +17,32 @@ app.addEventListener("click", async (event) => {
|
||||
app.addEventListener("input", (event) => {
|
||||
if (event.target.id === "global-search") {
|
||||
ui.search = event.target.value;
|
||||
render();
|
||||
document.querySelector("#global-search")?.focus();
|
||||
scheduleInputRender();
|
||||
} else if (event.target.id === "repo-filter") {
|
||||
ui.repoSearch = event.target.value;
|
||||
render();
|
||||
document.querySelector("#repo-filter")?.focus();
|
||||
scheduleInputRender();
|
||||
} else if (event.target.id === "commit-message") {
|
||||
ui.commitMessage = event.target.value;
|
||||
const position = event.target.selectionStart;
|
||||
render();
|
||||
const next = document.querySelector("#commit-message");
|
||||
if (next) {
|
||||
next.focus();
|
||||
next.setSelectionRange(position, position);
|
||||
const repository = selectedRepository();
|
||||
const hasSelection = Boolean(ui.selectedFiles.size || repository?.localStatus?.counts?.staged);
|
||||
const ready = Boolean(hasSelection && ui.commitMessage.trim());
|
||||
const blocker = !hasSelection
|
||||
? "Select files or stage one or more hunks."
|
||||
: ready
|
||||
? ui.selectedFiles.size
|
||||
? "Ready to commit. ForgeFlow stages the selected files automatically."
|
||||
: "Ready to commit only the reviewed staged hunks."
|
||||
: "Enter a commit message to enable commit and push.";
|
||||
const readiness = document.querySelector(".commit-readiness");
|
||||
if (readiness) {
|
||||
readiness.classList.toggle("ready", ready);
|
||||
readiness.classList.toggle("blocked", !ready);
|
||||
readiness.innerHTML = `${icon(ready ? "check" : "warning")}<span>${escapeHtml(blocker)}</span>`;
|
||||
}
|
||||
for (const button of document.querySelectorAll('[data-action="commit-push"], [data-action="commit-only"]')) {
|
||||
button.disabled = !ready;
|
||||
if (ready) button.removeAttribute("title");
|
||||
else button.title = blocker;
|
||||
}
|
||||
} else if (event.target.id === "setup-url")
|
||||
ui.setupDraft.baseUrl = event.target.value;
|
||||
@@ -38,7 +50,7 @@ app.addEventListener("input", (event) => {
|
||||
ui.setupDraft.token = event.target.value;
|
||||
else if (event.target.id === "palette-input") {
|
||||
ui.paletteQuery = event.target.value;
|
||||
render();
|
||||
scheduleInputRender(60);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -102,35 +114,32 @@ document.addEventListener("keydown", (event) => {
|
||||
}
|
||||
});
|
||||
|
||||
let pointerAnimationFrame = null;
|
||||
let pendingPointer = null;
|
||||
|
||||
document.addEventListener("pointermove", (event) => {
|
||||
const illustration = event.target.closest?.("[data-project-illustration]");
|
||||
if (illustration) {
|
||||
const bounds = illustration.getBoundingClientRect();
|
||||
illustration.style.setProperty(
|
||||
"--tilt-x",
|
||||
`${((event.clientY - bounds.top) / bounds.height - 0.5) * -7}deg`,
|
||||
);
|
||||
illustration.style.setProperty(
|
||||
"--tilt-y",
|
||||
`${((event.clientX - bounds.left) / bounds.width - 0.5) * 9}deg`,
|
||||
);
|
||||
}
|
||||
|
||||
const diffPanel = event.target.closest?.(".diff-panel");
|
||||
const atmosphere = diffPanel?.querySelector("[data-diff-atmosphere]");
|
||||
if (atmosphere) {
|
||||
const bounds = diffPanel.getBoundingClientRect();
|
||||
atmosphere.style.setProperty(
|
||||
"--diff-tilt-x",
|
||||
`${((event.clientY - bounds.top) / bounds.height - 0.5) * -3}deg`,
|
||||
);
|
||||
atmosphere.style.setProperty(
|
||||
"--diff-tilt-y",
|
||||
`${((event.clientX - bounds.left) / bounds.width - 0.5) * 4}deg`,
|
||||
);
|
||||
}
|
||||
pendingPointer = { target: event.target, clientX: event.clientX, clientY: event.clientY };
|
||||
if (pointerAnimationFrame) return;
|
||||
pointerAnimationFrame = requestAnimationFrame(() => {
|
||||
pointerAnimationFrame = null;
|
||||
const current = pendingPointer;
|
||||
pendingPointer = null;
|
||||
if (!current) return;
|
||||
const illustration = current.target.closest?.("[data-project-illustration]");
|
||||
if (illustration) {
|
||||
const bounds = illustration.getBoundingClientRect();
|
||||
illustration.style.setProperty("--tilt-x", `${((current.clientY - bounds.top) / bounds.height - 0.5) * -7}deg`);
|
||||
illustration.style.setProperty("--tilt-y", `${((current.clientX - bounds.left) / bounds.width - 0.5) * 9}deg`);
|
||||
}
|
||||
const diffPanel = current.target.closest?.(".diff-panel");
|
||||
const atmosphere = diffPanel?.querySelector("[data-diff-atmosphere]");
|
||||
if (atmosphere) {
|
||||
const bounds = diffPanel.getBoundingClientRect();
|
||||
atmosphere.style.setProperty("--diff-tilt-x", `${((current.clientY - bounds.top) / bounds.height - 0.5) * -3}deg`);
|
||||
atmosphere.style.setProperty("--diff-tilt-y", `${((current.clientX - bounds.left) / bounds.width - 0.5) * 4}deg`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener("pointerout", (event) => {
|
||||
const illustration = event.target.closest?.("[data-project-illustration]");
|
||||
if (illustration && !illustration.contains(event.relatedTarget)) {
|
||||
|
||||
Reference in New Issue
Block a user