Files
OSIT-AE-App-Svelte/documentation/AETHER_SVELTE5_PERFORMANCE_REVIEW.md
Scott Idem 5f2ccf8823 refactor(launcher): modularize launcher config and implement Phase 5 actuators
- Broke down the massive launcher_cfg.svelte into 7 modular sub-components.
- Updated electron_relay.ts with Phase 5 presentation controls and manifest tools.
- Updated architecture documentation to reflect the new TypeScript-based native bridge.
2026-01-26 16:18:00 -05:00

188 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Aether Svelte 5 App — Performance Review
Date: 2026-01-26
This document collects findings from a quick code review of the Aether Svelte 5 app (focused on `src/routes/+layout.svelte`, `src/routes/+page.svelte`, and the events launcher layout). It lists concrete performance issues observed, their likely impact, remediation recommendations (quick wins first), code examples for safe refactors, measurement steps, and a prioritized action plan.
**Scope**
- Files inspected: `src/routes/+layout.svelte`, `src/routes/+page.svelte`, event launcher `+layout.svelte` and related store initialization logic.
- Platform: SvelteKit with Vite (assumed from repo files). Builds use Tailwind CSS and manual global CSS.
---
## Summary of Key Findings
- Large synchronous imports at module evaluation time (notably `highlight.js` and its stylesheet) increase initial bundle size and run before first paint.
- Many components and icon imports are static at top-level even when rendered conditionally (`Analytics`, `MyClipboard`, `E_app_debug_menu`, `E_app_sys_menu`, multiple Lucide icons). This inflates the client bundle.
- Significant initialization and cache logic runs synchronously during module evaluation: store writes, cache expiration checks, IndexedDB operations, localStorage population, calls to `invalidateAll()` and conditional `window.location.reload()` paths — these can block initial render and cause reload loops or extra round trips.
- Dexie `liveQuery` usages in nested layouts are eager and may execute multiple DB queries on route entry, causing CPU and I/O work up-front.
- Multiple synchronous CSS imports (global CSS + highlight CSS) can delay first paint and increase transfer size.
- Many localStorage/sessionStorage writes and store updates are done immediately and synchronously; iterating many keys on startup can stall the main thread.
- Analytics, WebSocket connections, and external network resource usage may be triggered too early.
Impact: Slow Time to First Paint (TTFP), increased Time to Interactive (TTI), larger JS bundles, higher CPU main-thread blocking, worse Lighthouse/perceived performance.
---
## Concrete, Prioritized Recommendations
Priority ordering: Quick wins (low code risk) → Short-term (moderate refactor) → Long-term (architectural).
### Quick Wins (apply first)
- Lazy-load `highlight.js` and its CSS in `onMount` only when highlighting is needed. This removes a heavy library and stylesheet from the initial bundle.
- Dynamically import non-critical components (`Analytics`, debug menus, clipboard UI, large icon modules) when they will actually render.
- Move non-UI, non-blocking initialization to `onMount` and/or `requestIdleCallback` so SSR rendering and first paint aren't blocked by client-only logic.
- Guard `invalidateAll()` / `window.location.reload()` so they run only after a user-visible prompt or on explicit action; avoid automatic reload on subtle mismatches.
### Short-Term Changes (next 12 days)
- Defer expensive Dexie `liveQuery` and database queries until their containing route/section mounts, or add limits/pagination to reduce initial query size.
- Batch localStorage writes (use a single write or `requestIdleCallback`/`setTimeout(…,0)`), or store a single object rather than many keys.
- Replace heavy icon imports with an icon-on-demand strategy (SVG sprite, `unplugin-icons`, or inlining only icons used on first paint).
- Lazy-load large CSS (e.g., highlight theme) with dynamic import or by adding/removing link elements.
### Long-Term / Architectural
- Split routes to ensure route-level code-splitting is effective — verify that `+layout` doesn't import many route-specific modules. Keep `+layout.svelte` as thin as possible.
- Use a bundle analyzer to identify largest modules and tune dependencies.
- Serve production build with proper HTTP caching, compression (Brotli), and HTTP/2 or HTTP/3.
- Consider server-side rendering vs. partial hydration boundaries (islands) for heavy interactive regions.
---
## Concrete Code Examples
These are safe, copy-pasteable snippets for common quick fixes.
1) Lazy-load `highlight.js` in `src/routes/+layout.svelte` (move work to `onMount`):
```svelte
<script lang="ts">
import { onMount } from 'svelte';
let hljs: any;
onMount(async () => {
const mod = await import('highlight.js/lib/core');
hljs = mod.default || mod;
// register languages lazily
const xml = (await import('highlight.js/lib/languages/xml')).default;
const css = (await import('highlight.js/lib/languages/css')).default;
const js = (await import('highlight.js/lib/languages/javascript')).default;
const ts = (await import('highlight.js/lib/languages/typescript')).default;
hljs.registerLanguage('xml', xml);
hljs.registerLanguage('css', css);
hljs.registerLanguage('javascript', js);
hljs.registerLanguage('typescript', ts);
// load stylesheet dynamically if needed
await import('highlight.js/styles/github-dark.css');
});
</script>
```
2) Dynamic component import for `Analytics`:
```svelte
<script lang="ts">
import { onMount } from 'svelte';
let Analytics: any = null;
let showAnalytics = false;
onMount(async () => {
if (/* condition to show analytics, or simply true after idle */) {
const mod = await import('$lib/app_components/analytics.svelte');
Analytics = mod.default;
showAnalytics = true;
}
});
</script>
{#if showAnalytics && Analytics}
<svelte:component this={Analytics} bind:site_google_tracking_id={$ae_loc.site_google_tracking_id} />
{/if}
```
3) Batch localStorage writes using `requestIdleCallback` fallback:
```ts
function batchSetLocalStorage(obj: Record<string, any>) {
const work = () => {
for (const [k, v] of Object.entries(obj)) {
try { localStorage.setItem(k, JSON.stringify(v)); } catch (e) {}
}
};
if ('requestIdleCallback' in window) {
(window as any).requestIdleCallback(work);
} else {
setTimeout(work, 0);
}
}
```
4) Avoid automatic reloads — example guard:
```ts
// Only force reload after user consent or if critical mismatch
if (flag_reload) {
const reloadKey = 'aether_reload_shown_v' + ($ae_loc?.ver ?? 'unknown');
if (!localStorage.getItem(reloadKey)) {
if (confirm('A new version is available. Reload now?')) {
localStorage.setItem(reloadKey, '1');
location.reload();
}
}
}
```
---
## Measurement & Tooling
Run these locally to find concrete hotspots and bundle contributors.
- Bundle analysis (Vite):
```bash
# Build and open analyzer (if plugin configured)
npm run build
npx vite build --sourcemap
# Use source-map-explorer or webpack-bundle-analyzer (adapt if using Rollup)
npx source-map-explorer dist/assets/*.js
```
- Vite visualizer (install `rollup-plugin-visualizer` or `vite-plugin-visualizer`):
```bash
# add plugin and run build with report
# then open .html output from the plugin
```
- Lighthouse / DevTools:
- Run Lighthouse (Performance, TTI, Largest Contentful Paint, Main thread) in Chrome.
- In Performance panel record page load to find long main-thread tasks (JS parsing/execution) and identify blocking code paths.
- Runtime marks: add `performance.mark('init-start')` / `performance.mark('init-end')` to measure areas (e.g., store population, Dexie queries) then `performance.measure()`.
---
## Prioritized Action Plan (Checklist)
- [ ] Move `highlight.js` imports and theme stylesheet to `onMount` (quick win).
- [ ] Convert `Analytics` and other developer tools to dynamic imports (quick win).
- [ ] Move non-critical store population, IndexedDB maintenance, and cache-expiration logic to `onMount` or `requestIdleCallback`.
- [ ] Add guard & confirmation around `invalidateAll()` and `location.reload()` to avoid reload loops.
- [ ] Defer Dexie `liveQuery` initialization to the route or component that needs it (avoid running all live queries globally).
- [ ] Batch localStorage writes and avoid iterating thousands of keys synchronously.
- [ ] Run bundle analysis and produce a top-10 biggest modules list.
- [ ] Replace heavy icon imports with icon-on-demand solution.
- [ ] Re-run Lighthouse and validate improvements.
---
## Appendix: Quick Checklist for PRs
- Ensure client-only code is inside `onMount` or guarded by `if (browser)`.
- Where dynamic import used, use `<svelte:component this={Comp} />` pattern.
- Add performance marks around expensive blocks and log durations in dev mode.
- Avoid unconditional network or socket connections in top-level layout; open them after user interaction or when component mounts.
---
If you want, I can implement the first quick wins now: lazy-load `highlight.js` and convert `Analytics` import in `src/routes/+layout.svelte`, then run a local bundle analysis. Tell me which of the quick wins you'd like me to patch first and I'll start.