vojo/docs/plans/splash_mascot.md

6.4 KiB
Raw Blame History

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 splashpages/ConfigConfig.tsx renders <AuthSplashScreen /> (mascot + footer) while /config.json is fetched. ~50300ms on Caddy-served static.
  3. SpecVersions mascot splashpages/client/SpecVersions.tsx renders <AuthSplashScreen /> while /_matrix/client/versions is fetched from the homeserver. 0.52s, network-bound, the longest of the chain.
  4. ClientRoot mascot splashpages/client/ClientRoot.tsx loading || !mx ? <ClientRootLoading /> : .... Shown while initClient runs (IndexedDB open + crypto WASM init, ~300700ms) AND while waiting for SyncState.Prepared (the useSyncState gate at line ~200). The Prepared wait can be 130s 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 AppTheme.NoActionBarLaunchwindowBackground=@android:color/black. Cannot be fully eliminated; ~300500ms while Android starts the process and Capacitor loads the WebView.
  2. Then the web chain (steps 15 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.52s mascot in step 3.

Why reverted: it surfaced a latent bug in 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 returned null instead of <AuthSplashScreen />. 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 130s 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 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 <AuthSplashScreen>{retry-dialog}</AuthSplashScreen> 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.