# Splash & mascot — open notes State of play after the connection-indicator (`SyncIndicator`) work landed. Anything in this file is **deferred / future work**, not active. Treat it as context for the next person to pick up the splash topic. ## Current visible chain (cold load, web/desktop) 1. **Initial blank** — HTML loaded, React mounts. ~50ms. `body` background is `#0d0e11` (the `--vojo-safe-area-bg` var), so it's a dark frame, not white. 2. **`ConfigConfigLoading` mascot splash** — [`pages/ConfigConfig.tsx`](../../src/app/pages/ConfigConfig.tsx) renders `` (mascot + footer) while `/config.json` is fetched. ~50–300ms on Caddy-served static. 3. **`SpecVersions` mascot splash** — [`pages/client/SpecVersions.tsx`](../../src/app/pages/client/SpecVersions.tsx) renders `` while `/_matrix/client/versions` is fetched from the homeserver. **0.5–2s, network-bound, the longest of the chain.** 4. **`ClientRoot` mascot splash** — [`pages/client/ClientRoot.tsx`](../../src/app/pages/client/ClientRoot.tsx) `loading || !mx ? : ...`. Shown while `initClient` runs (IndexedDB open + crypto WASM init, ~300–700ms) AND while waiting for `SyncState.Prepared` (the `useSyncState` gate at line ~200). The Prepared wait can be 1–30s depending on cache warmth and account size. 5. App tree mounts. `SyncIndicator` takes over for the residual sync activity (green slide while sync is still settling, hidden once Syncing). ## Current visible chain (Android native) 1. **OS system splash** — Android 12+ enforces a system splash with the launcher icon centered over `windowBackground`. Configured via [`android/app/src/main/res/values/styles.xml`](../../android/app/src/main/res/values/styles.xml) `AppTheme.NoActionBarLaunch` → `windowBackground=@android:color/black`. Cannot be fully eliminated; ~300–500ms while Android starts the process and Capacitor loads the WebView. 2. Then the web chain (steps 1–5 above). So an Android cold-launch sees: black-icon (OS) → black blank (HTML) → mascot (config + spec-versions + initClient + Prepared) → app. ## What we tried and reverted (during the SyncIndicator work) In the 0.3.0 attempt that got reverted, we made several aggressive changes to shorten the chain. They worked, but introduced subtle bugs that the user chose to roll back to keep the diff clean. Specifically: ### A. SpecVersions made non-blocking [Commit reverted in `ff01e6c`.] The change made `SpecVersions` render children immediately with `versions: []` and hydrate the real value in the background (with retry on `online` event). This eliminated the 0.5–2s mascot in step 3. **Why reverted**: it surfaced a latent bug in [`components/message/content/ImageContent.tsx`](../../src/app/components/message/content/ImageContent.tsx) and `VideoContent.tsx`. While `versions: []`, `useMediaAuthentication()` returns false, autoPlay images load with unauth URLs, the server rejects, the local `error=true` flag latches and is never cleared on the post- hydration retry. The reviewer flagged this as a blocker. **Fix attempted**: reset `error` and `load` state in the `loadSrc`-deps useEffect AND in `handleLoad` — both `ImageContent.tsx` and `VideoContent.tsx`. The fix was correct but the user judged it as out-of- scope scope-creep for the connection-indicator feature and rolled the whole change back. **To redo cleanly**: ship SpecVersions-non-blocking and the ImageContent/VideoContent fix together, in a separate dedicated commit (scope = "make boot non-blocking", not bundled with the indicator). ### B. ClientRoot loading paths replaced with `null` [`pages/ConfigConfig.tsx::ConfigConfigLoading`](../../src/app/pages/ConfigConfig.tsx) returned `null` instead of ``. Same for the `!mx` branch in `ClientRoot`. Eliminated the mascot during steps 2 and 4-init-only. Reverted as part of the same revert. **To redo**: low risk on its own. Change those two callsites to render `null` (the dark body background shows through). Skip the Prepared gate removal (that's the deeper change). ### C. ClientRoot Prepared gate removed The ambitious move: render `MatrixClientProvider` as soon as `mx` is created (after `initClient`), not after `Prepared`. Eliminates the 1–30s wait for first sync — app shell renders against the IndexedDB cache while sync populates new events in background. **Why reverted**: needed an extra fix in [`pages/client/direct/RoomProvider.tsx`](../../src/app/pages/client/direct/RoomProvider.tsx) to add `useAtomValue(allRoomsAtom)` so cold-start deep-links (push tap) re-render when the room arrives. Plus arguably exposes a brief empty-UI flash on first-time logins. **To redo**: do it after a careful audit of every consumer of `mx.getRoom()` / `mx.getRooms()` to make sure they all tolerate empty stores. The deep-link re-render fix in `direct/RoomProvider.tsx` is the mechanical trigger; there may be others. ## What's open In rough priority order: 1. **SpecVersions non-blocking + media-auth race fix** (B above). Biggest win for cold-start time-to-interactive (~1.5s saved). Bundle with the ImageContent/VideoContent error reset in one commit. 2. **`null` instead of mascot during ConfigConfig + `!mx` loading** (B above, simpler half). ~400ms shaved. 3. **Drop the Prepared gate** (C above). Most invasive; needs full audit. Could wait until `dm_1x1_redesign.md` or another major-mode change is ready to absorb the risk. 4. **OS system splash on Android** — currently iconic-on-black via `AppTheme.NoActionBarLaunch`. Can't eliminate on Android 12+ but can be tuned (different background color, different icon) via `android/app/src/main/res/values/styles.xml`. Not user-reported as a problem, just noting the lever exists. ## Don't break - **`body { background: var(--vojo-safe-area-bg, #0d0e11) }`** in `src/index.css` — the dark frame is what makes a `null` loading path look intentional. Without it the user sees a white flash. - **Mascot on auth pages** (`/login`, `/register`) is INTENDED — those pages design the mascot in deliberately. Don't gate-remove it there. Only the loading-state mascot is the candidate for removal. - **The error-retry mascot in `ClientRoot`** (the `{retry-dialog}` branch in `ClientRoot.tsx`) is the rare case where the mascot is fine to keep — it's a hard-error context that needs anchoring for the dialog.