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

8.7 KiB
Raw Blame History

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):
<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>
  1. Dynamic component import for Analytics:
<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}
  1. Batch localStorage writes using requestIdleCallback fallback:
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);
  }
}
  1. Avoid automatic reloads — example guard:
// 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):
# 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):
# 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.