Files
eranos/src/main.tsx
T
Alex Gleason d9e943d03c Add kind 0 profile IndexedDB cache to eliminate author loading skeletons
Profile events (kind 0) are now persisted in IndexedDB and hydrated into
memory before React renders. useAuthor seeds TanStack Query with
initialData from the cache so names, avatars, and metadata render
instantly on repeat visits instead of showing skeletons.

Cache writes happen from three sites: useAuthor (single profile fetch),
useAuthors (batch fetch), and useFeed (community feed metadata prefetch).
Only newer events overwrite older ones to prevent out-of-order downgrades.

Staleness tiers match NIP-05: < 5 min fresh, 5 min - 7 d background
refetch, > 7 d expired (skeleton until fresh data arrives).
2026-03-20 21:52:48 -05:00

108 lines
4.2 KiB
TypeScript

import { createRoot } from 'react-dom/client';
// Import polyfills first
import './lib/polyfills.ts';
// Kick off cache hydration early so data is ready before components render.
import { hydrateNip05Cache } from '@/lib/nip05Cache';
import { hydrateProfileCache } from '@/lib/profileCache';
hydrateNip05Cache();
hydrateProfileCache();
import { ErrorBoundary } from '@/components/ErrorBoundary';
import App from './App.tsx';
import './index.css';
import '@fontsource-variable/inter';
// ─── Native status bar theming (Android APK / iOS) ───────────────────────────
// Keeps the OS top chrome in sync with the active app theme.
// Runs before React so the very first paint matches the persisted theme.
// Uses a MutationObserver so it reacts to all subsequent theme changes
// (class changes for builtin themes, style-content changes for custom themes).
import { Capacitor } from '@capacitor/core';
import { StatusBar, Style } from '@capacitor/status-bar';
if (Capacitor.isNativePlatform()) {
/**
* Read --background from the computed style of <html>, convert the HSL
* value to a hex color, and update the native status bar to match.
*
* Style.Dark = light/white icons (use on dark backgrounds)
* Style.Light = dark/black icons (use on light backgrounds)
*/
function updateStatusBar() {
const raw = getComputedStyle(document.documentElement)
.getPropertyValue('--background')
.trim();
if (!raw) return;
// --background is in shadcn/Tailwind HSL format: "H S% L%"
const parts = raw.replace(/%/g, '').split(/\s+/).map(Number);
if (parts.length < 3 || parts.some(isNaN)) return;
const [h, s, l] = parts;
// Convert HSL to RGB for luminance calculation
const sn = s / 100;
const ln = l / 100;
const k = (n: number) => (n + h / 30) % 12;
const a = sn * Math.min(ln, 1 - ln);
const f = (n: number) => ln - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
const r = Math.round(f(0) * 255);
const g = Math.round(f(8) * 255);
const b = Math.round(f(4) * 255);
// WCAG relative luminance
const toLinear = (v: number) => {
const n = v / 255;
return n <= 0.03928 ? n / 12.92 : Math.pow((n + 0.055) / 1.055, 2.4);
};
const luminance = 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
const isDark = luminance < 0.2;
const hex = '#' + [r, g, b].map(v => v.toString(16).padStart(2, '0')).join('');
StatusBar.setStyle({ style: isDark ? Style.Dark : Style.Light }).catch(() => {});
StatusBar.setBackgroundColor({ color: hex }).catch(() => {});
}
// Apply immediately (theme class is set synchronously by AppProvider useLayoutEffect
// before the first React paint, but we still try early in case it's already set).
updateStatusBar();
// Re-apply whenever the theme class changes on <html> (light / dark / custom)
const classObserver = new MutationObserver(() => updateStatusBar());
classObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class'],
});
// Re-apply whenever the injected <style id="theme-vars"> content changes
// (covers custom themes that change CSS variables without changing the class).
const styleObserver = new MutationObserver(() => updateStatusBar());
const observeThemeVars = () => {
const el = document.getElementById('theme-vars');
if (el) {
styleObserver.observe(el, { characterData: true, childList: true, subtree: true });
}
};
// The style element may not exist yet — watch <head> for it to appear.
observeThemeVars();
const headObserver = new MutationObserver(() => observeThemeVars());
headObserver.observe(document.head, { childList: true });
}
// ─────────────────────────────────────────────────────────────────────────────
createRoot(document.getElementById("root")!).render(
<ErrorBoundary>
<App />
</ErrorBoundary>
);
// Remove the HTML preloader after React has painted.
requestAnimationFrame(() => {
document.getElementById('preloader')?.remove();
});