Skip to content

Add "splash screen" to hide the slow loading of the app - #631

Open
Juanpe Bolívar (arximboldi) wants to merge 3 commits into
mainfrom
jp/splash-screen
Open

Add "splash screen" to hide the slow loading of the app#631
Juanpe Bolívar (arximboldi) wants to merge 3 commits into
mainfrom
jp/splash-screen

Conversation

@arximboldi

Copy link
Copy Markdown
Contributor

Summary

The app has grown a bit slow to load. Nothing too terrible but it freezes blank for a couple of seconds. This hides this with some nice animation to increase the subjective perception of fluidity.

Test Plan

  • Run the app :)

Docs

  • Docs updated (companion PR in darkmatter/nixmac-web: #___)
  • No docs update needed

The window is shown as soon as it is built, so the seconds spent hydrating
the ViewModel and running the launch probes read as an empty pane.

SplashScreen fills it with the mascot, the wordmark, and a label naming the
probe in flight. Deliberately the CSS-3D cube rather than <NixmacMascot3D>:
three.js must stay out of the main bundle, since it would add to the very
startup cost this screen exists to cover.

Its styling lives in a plain-CSS splash.css rather than Tailwind utilities so
that the pre-React boot splash can share it verbatim.
Replaces the neutral empty container the widget held before hydration, and
reports which launch probe is running as the mount effect walks them.
SplashScreen can only paint once the bundle has loaded and parsed, which is
itself part of the wait. index.html now carries the same markup inside #root,
styled by the same splash.css and gated on the same 400ms delay, with icon.svg
standing in for the animated mascot. React clears #root on its first commit,
so the handover costs nothing: the mark simply starts hopping.
@darkmatter

darkmatter Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

🎨 Storybook preview

Open Storybook preview

Updated for e1a4c1c


🧭 Story changes

Compared to main (snapshot diff at story level):

🆕 New stories (2)


⚠️ Detected UI changes (1)

These stories' HTML snapshots changed. I've added screenshots + links to the changed stories below. Review them carefully then accept the changes to regenerate baselines and include them in this PR:

Flows/Evolve › Playground

Flows/Evolve › Playground


Accept UI changes

  • Click here to accept these changes

Alternatively, you can run bun run test:update-snapshots locally to re-generate the baselines and then push the changes to this PR.

What does this do?

The screenshots above show UI changes detected by the Storybook
snapshot tests run on this PR. Each image is the rendered output of
a Storybook story from the code in this PR branch; the snapshot
test compared it against the committed baseline in
__snapshots__/ and flagged the difference.

Checking the box tells the darkmatter[bot] to regenerate the
baselines from this PR's current code and commit them directly to
this branch. The new baselines become the source of truth for
future runs — only accept after confirming the visual changes are
intentional.

Comparison baseline: the committed __snapshots__/ files on this
PR branch (carried forward from develop). Accept updates them in
place on this branch.

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor
Warnings
⚠️

No Linear issue ID found in this PR's title, description, or branch name (expected something like ENG-123). Add one so this work is traceable in Linear, or add #no-linear to the PR description to acknowledge it's intentionally untracked.

📋 PR Overview

Lines changed 615 (+608 / -7)
Files 4 added, 3 modified, 0 deleted
Draft / WIP no
Has Test Plan yes
Linear issue no
No Test Plan Needed no
New UI components yes (1)
New Storybook stories yes (1)
New Rust modules no
New TS source files yes (1)
New tests no
package.json touched no
Cargo.toml touched no
Infra / CI touched no

🔬 Coverage

Report Lines Statements Functions Branches
apps/native/coverage/coverage-summary.json 35.8% 35.4% 30.8% 30.0%

Generated by 🚫 dangerJS against e1a4c1c

Storybook stories need.
*/

.nixmac-splash {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider just leaving all these nixmac- prefixes out, they add a bit of verbosity for probably not a lot of gain when we already have the splash part.

@darkmatteragent darkmatteragent left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — COMMENT

e1a4c1ca3449 · 3 findings

Comment: the React handoff restarts the splash fade on normal slow boots, while the new CSS also degrades on the declared macOS floor.

Findings

Warning

major · correctness — Clamping elapsed delay resets the splash at handoff
apps/native/src/components/widget/layout/splash-screen.tsx:60

The static copy in index.html starts nixmac-splash-in after 400ms and fades for 500ms. When React replaces it after that threshold, this Math.max(0, ...) clamp forces the replacement animation to start at opacity 0. For example, a handoff at 600ms replaces a partially visible splash with a transparent element and replays the full fade; after 900ms it blanks an already opaque splash for anoth

Warning

major · compatibility — Splash layout breaks on the declared macOS minimum
apps/native/src/components/widget/layout/splash.css:23

The app declares macOS 10.13 as its minimum, whose Safari-era WKWebView predates the inset shorthand, so this is ignored and the absolutely positioned splash has no full-window offsets. The same new stylesheet's only backdrop, background: color-mix(in oklch, ...) at line 31, is also unsupported there. During the pre-bundle interval this can leave the splash content-sized with no backdrop; its

Important

minor · correctness — Stage-label fade animation is a no-op
apps/native/src/components/widget/layout/splash.css:103

.nixmac-splash__stage has no opacity declaration, so its underlying opacity is 1. The applied animation ends at opacity: 1 (line 108), and nixmac-splash-in has no explicit from keyframe; each keyed remount therefore animates 1→1 instead of fading in as the component comment claims. Add opacity: 0 to the stage rule or an explicit from { opacity: 0; } keyframe.

// in, this one must appear at once rather than restart the countdown. Frozen
// on first render — re-resolving `animation-delay` restarts the fade, and this
// component re-renders on every stage change.
const [delayMs] = useState(() => Math.max(0, appearDelayMs - performance.now()));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Clamping elapsed delay resets the splash at handoff

The static copy in index.html starts nixmac-splash-in after 400ms and fades for 500ms. When React replaces it after that threshold, this Math.max(0, ...) clamp forces the replacement animation to start at opacity 0. For example, a handoff at 600ms replaces a partially visible splash with a transparent element and replays the full fade; after 900ms it blanks an already opaque splash for another 500ms. Preserve the negative delay (appearDelayMs - performance.now()) so CSS starts the replacement at the elapsed animation progress.


.nixmac-splash {
position: absolute;
inset: 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Splash layout breaks on the declared macOS minimum

The app declares macOS 10.13 as its minimum, whose Safari-era WKWebView predates the inset shorthand, so this is ignored and the absolutely positioned splash has no full-window offsets. The same new stylesheet's only backdrop, background: color-mix(in oklch, ...) at line 31, is also unsupported there. During the pre-bundle interval this can leave the splash content-sized with no backdrop; its #fafafa text fallback then renders against the default white page. Add top/right/bottom/left and broadly supported background fallbacks, or raise the platform floor.

text-transform: uppercase;
letter-spacing: -0.01em;
color: var(--muted-foreground, #a1a1aa);
animation: nixmac-splash-in 300ms ease-out both;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[minor] Stage-label fade animation is a no-op

.nixmac-splash__stage has no opacity declaration, so its underlying opacity is 1. The applied animation ends at opacity: 1 (line 108), and nixmac-splash-in has no explicit from keyframe; each keyed remount therefore animates 1→1 instead of fading in as the component comment claims. Add opacity: 0 to the stage rule or an explicit from { opacity: 0; } keyframe.

@darkmatteragent

Copy link
Copy Markdown
Reviewer · darkmatter agent VERDICT
COMMENT
Comment: the React handoff restarts the splash fade on normal slow boots, while the new CSS also degrades on the declared macOS floor.

Warning

major · correctness — Clamping elapsed delay resets the splash at handoff
apps/native/src/components/widget/layout/splash-screen.tsx:60

The static copy in index.html starts nixmac-splash-in after 400ms and fades for 500ms. When React replaces it after that threshold, this Math.max(0, ...) clamp forces the replacement animation to start at opacity 0. For example, a handoff at 600ms replaces a partially visible splash with a transparent element and replays the full fade; after 900ms it blanks an already opaque splash for anoth

Warning

major · compatibility — Splash layout breaks on the declared macOS minimum
apps/native/src/components/widget/layout/splash.css:23

The app declares macOS 10.13 as its minimum, whose Safari-era WKWebView predates the inset shorthand, so this is ignored and the absolutely positioned splash has no full-window offsets. The same new stylesheet's only backdrop, background: color-mix(in oklch, ...) at line 31, is also unsupported there. During the pre-bundle interval this can leave the splash content-sized with no backdrop; its

Important

minor · correctness — Stage-label fade animation is a no-op
apps/native/src/components/widget/layout/splash.css:103

.nixmac-splash__stage has no opacity declaration, so its underlying opacity is 1. The applied animation ends at opacity: 1 (line 108), and nixmac-splash-in has no explicit from keyframe; each keyed remount therefore animates 1→1 instead of fading in as the component comment claims. Add opacity: 0 to the stage rule or an explicit from { opacity: 0; } keyframe.

reviewed: e1a4c1c — 2026-08-29T07:57Z verdict: comment findings: 3

Reviewer avatar Reviewer
darkmatter agent
Run details
Metadata Value
Agent darkmatter/agent/reviewer
Session pr:darkmatter/nixmac#631
Kind pr
Runtime platform-runtime
Configured model litellm/glm-5.2-fp8
Target darkmatter/nixmac#631
Revision e1a4c1ca344978f8e9e3d0cea07b3381be11a253
Verdict comment
Reviewed at 2026-08-29T07:57Z
Logs Open Braintrust session

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants