perf: reduce startup work and fix grade analytics bar chart animation

Batch settings storage writes, tier plugin startup, lazy-load heavy UI
chunks, and optimize global search indexing. Stop tweening bar height in
grade analytics to prevent invalid negative SVG rect values.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-17 10:50:26 +09:30
parent 62ed702e64
commit d10fca6c0f
41 changed files with 919 additions and 537 deletions
+31 -14
View File
@@ -3,11 +3,10 @@ import {
closeExtensionPopup,
SettingsClicked,
} from "../Closers/closeExtensionPopup";
import renderSvelte from "@/interface/main";
import { SettingsResizer } from "@/seqta/ui/SettingsResizer";
import Settings from "@/interface/pages/settings.svelte";
let isSettingsRendered = false;
let settingsLoadPromise: Promise<void> | null = null;
function extensionOutsideClickHandler(extensionPopup: HTMLElement) {
return (event: MouseEvent) => {
@@ -38,21 +37,39 @@ export function addExtensionSettings() {
(extensionContainer ?? document.body).addEventListener("click", handler, false);
}
export function renderSettingsIfNeeded() {
async function loadSettingsUi(extensionPopup: HTMLElement): Promise<void> {
if (isSettingsRendered) return;
const [{ default: renderSvelte }, { default: Settings }] = await Promise.all([
import("@/interface/main"),
import("@/interface/pages/settings.svelte"),
]);
const shadow = extensionPopup.attachShadow({ mode: "open" });
const mount = () => renderSvelte(Settings, shadow);
if ("requestIdleCallback" in window) {
requestIdleCallback(mount);
} else {
mount();
}
isSettingsRendered = true;
}
export async function renderSettingsIfNeeded(): Promise<void> {
if (isSettingsRendered) return;
const extensionPopup = document.getElementById("ExtensionPopup");
if (!extensionPopup) return;
try {
const shadow = extensionPopup.attachShadow({ mode: "open" });
if ('requestIdleCallback' in window) {
requestIdleCallback(() => renderSvelte(Settings, shadow));
} else {
renderSvelte(Settings, shadow);
}
isSettingsRendered = true;
} catch (err) {
console.error(err);
if (!settingsLoadPromise) {
settingsLoadPromise = loadSettingsUi(extensionPopup).catch((err) => {
settingsLoadPromise = null;
console.error(err);
throw err;
});
}
await settingsLoadPromise;
}
@@ -1,7 +1,7 @@
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
import { animate } from "motion";
import { settingsPopup } from "@/interface/hooks/SettingsPopup";
import { settingsPopup } from "@/seqta/utils/settingsPopup";
export let SettingsClicked = false;
@@ -785,7 +785,7 @@ export async function OpenThemeOfTheMonthPopup(
card.querySelector(".themeOfTheMonthCardPrimary")?.addEventListener("click", () => {
settingsState.themeOfTheMonthDismissedMonth = entry.month;
dismissWithCleanup();
openThemeStoreWithHighlight(linkedThemeId!);
void openThemeStoreWithHighlight(linkedThemeId!);
});
const openDontShowConfirm = () => {
@@ -37,6 +37,8 @@ const OPTIONAL_UNSET_MEANS_DEFAULT_KEYS = [
"profile_picture_revision",
] as const;
let defaultsEnsured = false;
/**
* Flat default map in upload shape (plugin-format only; no legacy keys).
*/
@@ -76,6 +78,8 @@ function mergePluginSettingsDefaults(
* Never overwrites existing values. Missing plugin settings respect legacy keys.
*/
export async function ensureSyncableStorageDefaults(): Promise<void> {
if (defaultsEnsured) return;
const existing = await browser.storage.local.get();
const migratedFromExisting = migrateLegacyToPluginSettings({
...existing,
@@ -101,4 +105,6 @@ export async function ensureSyncableStorageDefaults(): Promise<void> {
if (Object.keys(patch).length > 0) {
await browser.storage.local.set(patch);
}
defaultsEnsured = true;
}
+29 -2
View File
@@ -3,6 +3,7 @@ interface EventListenerOptions {
textContent?: string;
className?: string;
id?: string;
selector?: string;
customCheck?: (element: Element) => boolean;
once?: boolean;
parentElement?: Element;
@@ -20,6 +21,7 @@ class EventManager {
private listeners: Map<string, EventListener[]> = new Map();
private mutationObservers: Map<Element, MutationObserver> = new Map();
private pendingElements: Set<Element> = new Set();
private firedOnceIds: Set<string> = new Set();
private throttleTimeout: number = 5; // 5ms throttle
private throttleTimer: number | undefined;
private chunkSize: number = 50; // Process 50 elements per chunk
@@ -58,6 +60,7 @@ class EventManager {
}
private buildSelector(options: EventListenerOptions): string | null {
if (options.selector) return options.selector;
if (options.textContent || options.customCheck) return null;
let selector = options.elementType || "";
@@ -71,6 +74,23 @@ class EventManager {
return selector.trim() || null;
}
private getElementsToCheck(
element: Element,
options: EventListenerOptions,
): Element[] {
const selector = this.buildSelector(options);
if (!selector) return [element];
const targets = new Set<Element>();
if (element.matches(selector)) {
targets.add(element);
}
for (const match of element.querySelectorAll(selector)) {
targets.add(match);
}
return Array.from(targets);
}
private async scanExistingElements(
options: EventListenerOptions,
callback: (element: Element) => void,
@@ -174,10 +194,17 @@ class EventManager {
private async checkElement(element: Element): Promise<void> {
for (const [event, listeners] of this.listeners.entries()) {
for (const { id, options, callback } of listeners) {
if (this.matchesOptions(element, options)) {
callback(element);
if (options.once && this.firedOnceIds.has(id)) continue;
const targets = this.getElementsToCheck(element, options);
for (const target of targets) {
if (!this.matchesOptions(target, options)) continue;
callback(target);
if (options.once) {
this.firedOnceIds.add(id);
this.unregisterById(event, id);
break;
}
}
}
+60 -31
View File
@@ -6,15 +6,20 @@ import {
OpenMenuOptions,
} from "@/seqta/utils/Openers/OpenMenuOptions";
import { ThemeManager } from "@/plugins/built-in/themes/theme-manager";
import {
CloseThemeCreator,
OpenThemeCreator,
} from "@/plugins/built-in/themes/ThemeCreator";
import sendThemeUpdate from "@/seqta/utils/sendThemeUpdate";
import hideSensitiveContent from "@/seqta/ui/dev/hideSensitiveContent";
import type { ThemeManager } from "@/plugins/built-in/themes/theme-manager";
const themeManager = ThemeManager.getInstance();
let themeManagerPromise: Promise<ThemeManager> | null = null;
function getThemeManager(): Promise<ThemeManager> {
if (!themeManagerPromise) {
themeManagerPromise = import("@/plugins/built-in/themes/theme-manager").then(
({ ThemeManager }) => ThemeManager.getInstance(),
);
}
return themeManagerPromise;
}
export class MessageHandler {
constructor() {
@@ -34,6 +39,7 @@ export class MessageHandler {
case "UpdateThemePreview":
if (request?.save == true) {
const save = async () => {
const themeManager = await getThemeManager();
await themeManager.saveTheme({
...request.body,
userEdited: true,
@@ -44,65 +50,88 @@ export class MessageHandler {
sendResponse({ status: "success" });
sendThemeUpdate();
};
save();
void save();
} else {
themeManager.updatePreview(request.body);
sendResponse({ status: "success" });
void getThemeManager().then((themeManager) => {
themeManager.updatePreview(request.body);
sendResponse({ status: "success" });
});
}
return true;
case "GetTheme":
themeManager.getTheme(request.body.themeID).then((theme) => {
sendResponse(theme);
void getThemeManager().then((themeManager) => {
themeManager.getTheme(request.body.themeID).then((theme) => {
sendResponse(theme);
});
});
return true;
case "SetTheme":
themeManager.setTheme(request.body.themeID).then(() => {
sendResponse({ status: "success" });
void getThemeManager().then((themeManager) => {
themeManager.setTheme(request.body.themeID).then(() => {
sendResponse({ status: "success" });
});
});
return true;
case "DisableTheme":
themeManager.disableTheme().then(() => {
sendResponse({ status: "success" });
void getThemeManager().then((themeManager) => {
themeManager.disableTheme().then(() => {
sendResponse({ status: "success" });
});
});
return true;
case "DeleteTheme":
themeManager.deleteTheme(request.body.themeID).then(() => {
sendResponse({ status: "success" });
void getThemeManager().then((themeManager) => {
themeManager.deleteTheme(request.body.themeID).then(() => {
sendResponse({ status: "success" });
});
});
return true;
case "ListThemes":
themeManager.getAvailableThemes().then((themes) => {
sendResponse(themes);
void getThemeManager().then((themeManager) => {
themeManager.getAvailableThemes().then((themes) => {
sendResponse(themes);
});
});
return true;
case "OpenThemeCreator":
case "OpenThemeCreator": {
const themeID = request?.body?.themeID;
OpenThemeCreator(themeID ? themeID : "");
void import("@/plugins/built-in/themes/ThemeCreator").then(
({ OpenThemeCreator }) => {
void OpenThemeCreator(themeID ? themeID : "");
},
);
closeExtensionPopup();
sendResponse({ status: "success" });
break;
}
case "ShareTheme":
themeManager.shareTheme(request.body.themeID).then((id) => {
sendResponse({ status: "success", id });
void getThemeManager().then((themeManager) => {
themeManager.shareTheme(request.body.themeID).then((id) => {
sendResponse({ status: "success", id });
});
});
return true;
case "CloseThemeCreator":
try {
CloseThemeCreator();
sendResponse({ status: "success" });
} catch (error) {
console.error("Error closing theme creator:", error);
sendResponse({ status: "error" });
}
break;
void import("@/plugins/built-in/themes/ThemeCreator").then(
({ CloseThemeCreator }) => {
try {
CloseThemeCreator();
sendResponse({ status: "success" });
} catch (error) {
console.error("Error closing theme creator:", error);
sendResponse({ status: "error" });
}
},
);
return true;
case "HideSensitive":
hideSensitiveContent();
+59 -10
View File
@@ -16,6 +16,23 @@ function isExcludedSettingsKey(key: string): boolean {
return EXCLUDED_FROM_SETTINGS_SURFACE.has(key);
}
const SAVE_DEBOUNCE_MS = 200;
function storageChangeIsNoop(oldValue: unknown, newValue: unknown): boolean {
if (oldValue === newValue) return true;
if (
oldValue === undefined ||
newValue === undefined ||
typeof oldValue !== "object" ||
typeof newValue !== "object" ||
oldValue === null ||
newValue === null
) {
return false;
}
return JSON.stringify(oldValue) === JSON.stringify(newValue);
}
type ChangeListener = (newValue: any, oldValue: any) => void;
type GlobalChangeListener = (newValue: any, oldValue: any, key: string) => void;
@@ -25,9 +42,11 @@ class StorageManager {
private listeners: Map<string, Set<ChangeListener>>;
private globalListeners: Set<GlobalChangeListener>;
private subscribers: Set<Subscriber<SettingsState>> = new Set();
private saveTimeout: NodeJS.Timeout | null = null;
private saveTimeout: ReturnType<typeof setTimeout> | null = null;
private pendingPatch: Record<string, unknown> = {};
private initialized = false;
private bootstrapping = false;
private suppressWrites = false;
private constructor() {
this.data = {} as SettingsState;
@@ -151,12 +170,14 @@ class StorageManager {
});
}
public async saveToStorage(changedKeys?: string[]): Promise<void> {
if (this.saveTimeout) {
clearTimeout(this.saveTimeout);
this.saveTimeout = null;
public setSuppressWrites(suppress: boolean): void {
this.suppressWrites = suppress;
if (!suppress) {
this.scheduleDebouncedSave();
}
const payload: Record<string, unknown> = {};
}
private queueStoragePatch(changedKeys?: string[]): void {
const keys =
changedKeys && changedKeys.length > 0
? changedKeys
@@ -166,18 +187,42 @@ class StorageManager {
if (isExcludedSettingsKey(key)) continue;
const value = (this.data as Record<string, unknown>)[key];
if (value !== undefined) {
payload[key] = value;
this.pendingPatch[key] = value;
}
}
}
if (Object.keys(payload).length === 0) return;
private scheduleDebouncedSave(): void {
if (this.bootstrapping || this.suppressWrites) return;
if (Object.keys(this.pendingPatch).length === 0) return;
await browser.storage.local.set(payload);
if (this.saveTimeout) {
clearTimeout(this.saveTimeout);
}
this.saveTimeout = setTimeout(() => {
void this.flushPendingPatch();
}, SAVE_DEBOUNCE_MS);
}
private async flushPendingPatch(): Promise<void> {
this.saveTimeout = null;
if (this.bootstrapping || this.suppressWrites) return;
const patch = { ...this.pendingPatch };
this.pendingPatch = {};
if (Object.keys(patch).length === 0) return;
await browser.storage.local.set(patch);
if (!this.bootstrapping) {
this.notifySubscribers();
}
}
public saveToStorage(changedKeys?: string[]): void {
this.queueStoragePatch(changedKeys);
this.scheduleDebouncedSave();
}
private async removeFromStorage(key: string): Promise<void> {
await browser.storage.local.remove(key);
}
@@ -189,7 +234,7 @@ class StorageManager {
const actualChanges: string[] = [];
for (const [key, { oldValue, newValue }] of Object.entries(changes)) {
if (JSON.stringify(oldValue) === JSON.stringify(newValue)) continue;
if (storageChangeIsNoop(oldValue, newValue)) continue;
if (isExcludedSettingsKey(key)) continue;
if (newValue !== undefined) {
@@ -292,3 +337,7 @@ class StorageManager {
export const settingsState = StorageManager.getInstance();
export const initializeSettingsState = async () =>
await StorageManager.initialize();
export function setSettingsStateSuppressWrites(suppress: boolean): void {
settingsState.setSuppressWrites(suppress);
}
+3 -17
View File
@@ -1,30 +1,15 @@
import { OpenStorePage } from "@/seqta/ui/renderStore";
/**
* Module-level handoff for "open the theme store and highlight this theme".
*
* The store page is mounted lazily inside a Shadow DOM the first time it
* opens, so a `CustomEvent` listener would have to be wired up before mount
* (causing a race). Using a shared cell keeps the producer (popup button) and
* consumer (store `onMount`) decoupled without that timing constraint.
*
* The store reads & clears this on mount via {@link consumePendingHighlightThemeId}.
*/
let pendingHighlightThemeId: string | null = null;
/** Read and clear the pending theme id (called by the store on mount). */
export function consumePendingHighlightThemeId(): string | null {
const id = pendingHighlightThemeId;
pendingHighlightThemeId = null;
return id;
}
/**
* Opens the theme store and asks it to focus / highlight the given theme.
* If the store is already mounted we dispatch a DOM event so it can react
* without remounting; otherwise the store consumes the pending id on mount.
*/
export function openThemeStoreWithHighlight(themeId: string): void {
export async function openThemeStoreWithHighlight(themeId: string): Promise<void> {
pendingHighlightThemeId = themeId;
const existing = document.getElementById("store");
@@ -35,5 +20,6 @@ export function openThemeStoreWithHighlight(themeId: string): void {
return;
}
OpenStorePage();
const { OpenStorePage } = await import("@/seqta/ui/renderStore");
await OpenStorePage();
}
+33
View File
@@ -0,0 +1,33 @@
type SettingsPopupCallback = () => void;
/**
* Singleton that notifies listeners when the in-page settings popup closes.
* Used by the colour picker and other overlays tied to ExtensionPopup.
*/
class SettingsPopup {
private static instance: SettingsPopup;
private listeners: Set<SettingsPopupCallback> = new Set();
private constructor() {}
public static getInstance(): SettingsPopup {
if (!SettingsPopup.instance) {
SettingsPopup.instance = new SettingsPopup();
}
return SettingsPopup.instance;
}
public addListener(callback: SettingsPopupCallback): void {
this.listeners.add(callback);
}
public removeListener(callback: SettingsPopupCallback): void {
this.listeners.delete(callback);
}
public triggerClose(): void {
this.listeners.forEach((callback) => callback());
}
}
export const settingsPopup = SettingsPopup.getInstance();
+1 -1
View File
@@ -17,7 +17,7 @@ export function setupSettingsButton() {
if (SettingsClicked) {
closeExtensionPopup(extensionPopup as HTMLElement);
} else {
renderSettingsIfNeeded();
await renderSettingsIfNeeded();
await delay(30);
+41 -47
View File
@@ -1,12 +1,9 @@
import { eventManager } from "@/seqta/utils/listeners/EventManager";
import { delay } from "@/seqta/utils/delay";
/**
* Asynchronously waits for an element to be present in the DOM.
*
* This function can use either a polling mechanism (via `setTimeout`) or
* a `MutationObserver` (via `eventManager.register`) to detect the element.
* By default, it uses the `eventManager` which is more efficient.
* By default uses direct `querySelector` plus a targeted `MutationObserver`
* on `document.documentElement`. Polling via `setTimeout` is available as a
* fallback when `usePolling` is true.
*
* @param {string} selector The CSS selector for the target element.
* @param {boolean} [usePolling=false] If true, forces the use of `setTimeout` for polling.
@@ -24,9 +21,6 @@ export async function waitForElm(
if (usePolling) {
return new Promise((resolve, reject) => {
let iterations = 0;
if (maxIterations) {
iterations = 0;
}
const checkForElement = () => {
const element = document.querySelector(selector);
if (element) {
@@ -36,6 +30,7 @@ export async function waitForElm(
iterations++;
if (iterations >= maxIterations) {
reject(new Error("Element not found"));
return;
}
}
setTimeout(checkForElement, interval);
@@ -43,47 +38,46 @@ export async function waitForElm(
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", checkForElement);
document.addEventListener("DOMContentLoaded", checkForElement, {
once: true,
});
} else {
checkForElement();
}
});
} else {
return new Promise((resolve) => {
const registerObserver = () => {
const { unregister } = eventManager.register(
`${selector}`,
{
customCheck: (element) => element.matches(selector),
},
async (element) => {
resolve(element);
await delay(1);
unregister(); // Remove the listener once the element is found
},
);
return unregister;
};
let unregister = null;
if (document.readyState === "loading") {
// DOM is still loading, wait for it to be ready
document.addEventListener("DOMContentLoaded", () => {
unregister = registerObserver();
});
} else {
unregister = registerObserver();
}
const querySelector = () => document.querySelector(selector);
const element = querySelector();
if (element) {
if (unregister) unregister();
resolve(element);
return;
}
});
}
return new Promise((resolve) => {
const tryResolve = (): boolean => {
const element = document.querySelector(selector);
if (element) {
resolve(element);
return true;
}
return false;
};
const startObserver = () => {
if (tryResolve()) return;
const observer = new MutationObserver(() => {
if (tryResolve()) {
observer.disconnect();
}
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", startObserver, {
once: true,
});
} else {
startObserver();
}
});
}