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.
This commit is contained in:
@@ -7,39 +7,37 @@ The Aether Native App serves as the OS-level bridge for the SvelteKit frontend.
|
||||
|
||||
## 2. The Three-Layer Architecture
|
||||
|
||||
### 2.1 The Engine (`electron_native.js`)
|
||||
* **Process:** Main Process (Node.js)
|
||||
### 2.1 The Engine (`src/main/*.ts`)
|
||||
* **Process:** Main Process (Node.js/TypeScript)
|
||||
* **Role:** Performs the actual OS-level operations.
|
||||
* **Key Responsibilities:**
|
||||
* Executing shell commands via `child_process`.
|
||||
* Managing the permanent Hashed Cache (`file_cache/`).
|
||||
* Performing atomic "Safe Handover" copies to the operational `temp/` directory.
|
||||
* Resolving global path placeholders like `[home]` and `[tmp]`.
|
||||
* **Shell Handlers (`shell_handlers.ts`)**: Executing shell commands, AppleScripts, and process management.
|
||||
* **File Handlers (`file_handlers.ts`)**: Managing the permanent Hashed Cache (`file_cache/`) and atomic Safe Handover.
|
||||
* Resolving global path placeholders like `[home]` and `[tmp]` via `file_utils.ts`.
|
||||
|
||||
### 2.2 The Bridge (`preload.js`)
|
||||
### 2.2 The Bridge (`src/preload/index.ts`)
|
||||
* **Process:** Preload Script
|
||||
* **Role:** The security gatekeeper.
|
||||
* **Key Responsibilities:**
|
||||
* Uses Electron's `contextBridge` to securely expose specific Main Process functions to the UI.
|
||||
* Uses Electron's `contextBridge` to securely expose Main Process functions to the UI.
|
||||
* Exposes the `window.aetherNative` object.
|
||||
* Ensures that only whitelisted IPC channels can be triggered by the renderer.
|
||||
|
||||
### 2.3 The Messenger (`electron_relay.ts`)
|
||||
### 2.3 The Messenger (`src/lib/electron/electron_relay.ts`)
|
||||
* **Process:** Renderer Process (SvelteKit)
|
||||
* **Role:** The TypeScript API used by Svelte components.
|
||||
* **Key Responsibilities:**
|
||||
* Detecting if the app is running in "Native Mode".
|
||||
* Providing a clean, typed interface for native calls.
|
||||
* Implementing "Smart Fallbacks" (e.g., resolving placeholders UI-side if the bridge is outdated).
|
||||
* Standardizing IPC calls to snake_case.
|
||||
* Implementing "Smart Fallbacks" (e.g., UI-side placeholder resolution if the bridge is inactive).
|
||||
|
||||
---
|
||||
|
||||
## 3. Core Lifecycle
|
||||
|
||||
1. **Seed Phase:** The Electron shell reads `~/seed.json` to identify the device.
|
||||
2. **Hydration Phase:** The shell authenticates with the Aether V3 API to pull device-specific settings (room assignment, sync timers).
|
||||
3. **Injection Phase:** The shell injects the **JWT (Auth Token)** and **Native Config** into the SvelteKit environment.
|
||||
4. **Observation Phase:** `LauncherBackgroundSync.svelte` force-hydrates absolute OS paths (Home/Tmp) into the global `ae_loc` store for immediate use.
|
||||
1. **Seed Phase:** The Electron shell reads `resources/seed_config.json` to identify the device.
|
||||
2. **Hydration Phase:** The shell authenticates with the Aether V3 API to pull device-specific settings.
|
||||
3. **Injection Phase:** The shell injects the **JWT** and **Native Config** into the SvelteKit environment.
|
||||
4. **Observation Phase:** Background sync loops manage file warming and heartbeat telemetry.
|
||||
|
||||
---
|
||||
|
||||
@@ -48,17 +46,20 @@ The Aether Native App serves as the OS-level bridge for the SvelteKit frontend.
|
||||
### File & Cache Management
|
||||
- `check_cache({hash})`: Verify if a file exists in the hidden hashed storage.
|
||||
- `download_to_cache({url, hash})`: Secure background download using the native API key.
|
||||
- `launch_from_cache({hash, filename})`: **Safe Handover** — copies from cache to temp with the original name and triggers the launcher.
|
||||
- `launch_from_cache({hash, filename})`: **Safe Handover** — copies from cache to temp with the original name and triggers the specialized launcher.
|
||||
|
||||
### OS Execution (The "Actuators")
|
||||
- `launch_presentation({path, app})`: Intelligent application selection.
|
||||
- `launch_presentation({path, app})`: Phase 5 specialized launcher with auto-focus (`activate`) and slideshow start.
|
||||
- **Linux:** Triggers `libreoffice --impress`.
|
||||
- **macOS:** Triggers AppleScript for Keynote/PowerPoint.
|
||||
- `run_cmd({cmd})`: Executes raw shell commands with automatic `[home]` and `[tmp]` resolution.
|
||||
- `kill_processes([names])`: Force-closes presentation apps to clean the podium between sessions.
|
||||
- **macOS:** Triggers AppleScript for Keynote or PowerPoint.
|
||||
- `control_presentation({app, action})`: Remote navigation for active decks.
|
||||
- **Actions:** `next`, `prev`, `start`, `stop`.
|
||||
- `run_cmd({cmd})`: Executes raw shell commands with automatic path resolution.
|
||||
- `kill_processes([names])`: Force-closes apps to clean the podium.
|
||||
|
||||
### System Metadata
|
||||
- `get_device_info()`: Provides OS version, CPU/RAM stats, and absolute system paths.
|
||||
### Metadata & Discovery
|
||||
- `get_device_info()`: Provides CPU/RAM stats and absolute system paths.
|
||||
- `list_tools()`: Returns a JSON manifest of all available native functions for self-documentation.
|
||||
|
||||
---
|
||||
|
||||
|
||||
187
documentation/AETHER_SVELTE5_PERFORMANCE_REVIEW.md
Normal file
187
documentation/AETHER_SVELTE5_PERFORMANCE_REVIEW.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# 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 1–2 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.
|
||||
Reference in New Issue
Block a user